mirror of
https://github.com/supabase/supabase.git
synced 2026-10-10 03:45:06 +03:00
Merge remote-tracking branch 'origin/master' into jordi/migrate-legacy-logs-to-otel
# Conflicts: # apps/studio/package.json # pnpm-lock.yaml
This commit is contained in:
commit
536c85b3ab
426 files changed
+13735
-12034
No files matched your search
Binary file not shown.
|
After Width: | Height: | Size: 405 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 424 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 454 KiB |
@@ -23,7 +23,8 @@ jobs:
|
||||
tests_ran: ${{ steps.filter.outputs.studio == 'true' }}
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
contents: read
|
||||
id-token: write
|
||||
env:
|
||||
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
|
||||
|
||||
@@ -83,6 +84,18 @@ jobs:
|
||||
if: steps.filter.outputs.studio == 'true'
|
||||
run: rm -rf supabase && pnpm exec supabase init && mkdir supabase/functions
|
||||
|
||||
# Authenticate with AWS ECR to avoid rate limiting
|
||||
- name: configure aws credentials
|
||||
if: steps.filter.outputs.studio == 'true' && !github.event.pull_request.head.repo.fork
|
||||
uses: aws-actions/configure-aws-credentials@5fd3084fc36e372ff1fff382a39b10d03659f355 # v2.2.0
|
||||
with:
|
||||
role-to-assume: ${{ secrets.PROD_AWS_ROLE }}
|
||||
aws-region: us-east-1
|
||||
- uses: docker/login-action@465a07811f14bebb1938fbed4728c6a1ff8901fc # v2.2.0
|
||||
if: steps.filter.outputs.studio == 'true' && !github.event.pull_request.head.repo.fork
|
||||
with:
|
||||
registry: public.ecr.aws
|
||||
|
||||
- name: Pre-start diagnostics
|
||||
run: |
|
||||
docker ps -a
|
||||
|
||||
@@ -22,6 +22,9 @@ apps/**/.turbo
|
||||
apps/docs/CONTRIBUTING.md
|
||||
apps/docs/__generated__
|
||||
apps/design-system/__registry__
|
||||
# TanStack Router auto-generated route tree; the file header explicitly
|
||||
# says to exclude it from formatters.
|
||||
apps/studio/routeTree.gen.ts
|
||||
packages/icons/__registry__
|
||||
packages/icons/src/icons/*.ts
|
||||
apps/ui-library/__registry__
|
||||
|
||||
@@ -48,7 +48,7 @@ import {
|
||||
BreadcrumbList,
|
||||
BreadcrumbPage,
|
||||
BreadcrumbSeparator,
|
||||
} from '@/components/ui/breadcrumb'
|
||||
} from 'ui'
|
||||
```
|
||||
|
||||
```tsx
|
||||
@@ -111,7 +111,7 @@ import {
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu"
|
||||
} from "ui"
|
||||
|
||||
...
|
||||
|
||||
@@ -139,7 +139,7 @@ We provide a `<BreadcrumbEllipsis />` component to show a collapsed state when t
|
||||
<ComponentPreview name="breadcrumb-ellipsis" className="[&_.preview]:p-2" />
|
||||
|
||||
```tsx showLineNumbers {1,9}
|
||||
import { BreadcrumbEllipsis } from "@/components/ui/breadcrumb"
|
||||
import { BreadcrumbEllipsis } from "ui"
|
||||
|
||||
...
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@hookform/resolvers": "^3.1.1",
|
||||
"@tanstack/react-table": "^8.21.3",
|
||||
"@tanstack/react-table": "catalog:",
|
||||
"contentlayer2": "0.4.6",
|
||||
"common": "workspace:*",
|
||||
"date-fns": "^2.30.0",
|
||||
|
||||
@@ -32,6 +32,7 @@ public/llms/
|
||||
# Generated guide and reference markdown files
|
||||
public/markdown/
|
||||
public/docs.tar.gz
|
||||
public/docs/
|
||||
|
||||
# Copied examples folder
|
||||
/examples/
|
||||
|
||||
@@ -26,7 +26,6 @@ For content that requires progressive disclosure:
|
||||
size="medium"
|
||||
className="text-foreground-light mt-8 mb-6"
|
||||
>
|
||||
<div className="border-b mt-3 pb-3">
|
||||
<AccordionItem
|
||||
header="Accordion item 1"
|
||||
id="item-1"
|
||||
@@ -36,8 +35,6 @@ For content that requires progressive disclosure:
|
||||
|
||||
</AccordionItem>
|
||||
|
||||
</div>
|
||||
<div className="border-b mt-3 pb-3">
|
||||
<AccordionItem
|
||||
header="Accordion item 2"
|
||||
id="item-2"
|
||||
@@ -47,7 +44,6 @@ For content that requires progressive disclosure:
|
||||
|
||||
</AccordionItem>
|
||||
|
||||
</div>
|
||||
</Accordion>
|
||||
```
|
||||
|
||||
@@ -59,8 +55,7 @@ For content that requires progressive disclosure:
|
||||
size="medium"
|
||||
className="text-foreground-light mt-8 mb-6"
|
||||
>
|
||||
<div className="border-b mt-3 pb-3">
|
||||
<AccordionItem
|
||||
<AccordionItem
|
||||
header="Accordion item 1"
|
||||
id="item-1"
|
||||
>
|
||||
@@ -69,9 +64,7 @@ For content that requires progressive disclosure:
|
||||
|
||||
</AccordionItem>
|
||||
|
||||
</div>
|
||||
<div className="border-b mt-3 pb-3">
|
||||
<AccordionItem
|
||||
<AccordionItem
|
||||
header="Accordion item 2"
|
||||
id="item-2"
|
||||
>
|
||||
@@ -80,7 +73,6 @@ For content that requires progressive disclosure:
|
||||
|
||||
</AccordionItem>
|
||||
|
||||
</div>
|
||||
</Accordion>
|
||||
|
||||
### Admonition
|
||||
@@ -277,6 +269,7 @@ You can also import the `supabase-js` library here:
|
||||
````mdx
|
||||
```js
|
||||
import { createClient } from '@supabase/supabase-js'
|
||||
|
||||
const supabase = createClient('dummy', 'client')
|
||||
|
||||
// ---cut---
|
||||
@@ -291,6 +284,7 @@ Note the hidden statements above the cut. Hover over `signInWithPassword` to see
|
||||
|
||||
```js
|
||||
import { createClient } from '@supabase/supabase-js'
|
||||
|
||||
const supabase = createClient('dummy', 'client')
|
||||
|
||||
// ---cut---
|
||||
@@ -513,8 +507,7 @@ We incorporate content reuse in the docs to avoid duplication. If you find yours
|
||||
size="medium"
|
||||
className="text-foreground-light mt-8 mb-6"
|
||||
>
|
||||
<div className="border-b mt-3 pb-3">
|
||||
<AccordionItem
|
||||
<AccordionItem
|
||||
header="Database setup"
|
||||
id="database-setup"
|
||||
>
|
||||
@@ -527,9 +520,7 @@ We incorporate content reuse in the docs to avoid duplication. If you find yours
|
||||
|
||||
</AccordionItem>
|
||||
|
||||
</div>
|
||||
<div className="border-b mt-3 pb-3">
|
||||
<AccordionItem
|
||||
<AccordionItem
|
||||
header="Create client for Auth"
|
||||
id="create-client-auth"
|
||||
>
|
||||
@@ -542,7 +533,6 @@ We incorporate content reuse in the docs to avoid duplication. If you find yours
|
||||
|
||||
</AccordionItem>
|
||||
|
||||
</div>
|
||||
</Accordion>
|
||||
|
||||
To make a new partial:
|
||||
|
||||
@@ -2773,6 +2773,10 @@ export const platform: NavMenuConstant = {
|
||||
},
|
||||
{ name: 'Performance Tuning', url: '/guides/platform/performance' as `/${string}` },
|
||||
{ name: 'SSL Enforcement', url: '/guides/platform/ssl-enforcement' as `/${string}` },
|
||||
{
|
||||
name: 'Postgres Connection Logging',
|
||||
url: '/guides/platform/postgres-connection-logging' as `/${string}`,
|
||||
},
|
||||
{
|
||||
name: 'Default Platform Permissions',
|
||||
url: '/guides/platform/permissions' as `/${string}`,
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
// Shared copy for the Realtime throughput tables. Consumed both by the
|
||||
// interactive component and by the markdown alternative in
|
||||
// `internals/markdown-schema/RealtimeLimitsEstimator.ts`, so the headings and
|
||||
// compute labels stay in sync. Keep this file free of React/browser imports.
|
||||
|
||||
export const COMPUTE_OPTIONS = [
|
||||
{ value: 'micro', label: 'Micro' },
|
||||
{ value: 'small', label: 'Small to medium' },
|
||||
{ value: 'large', label: 'Large to 16XL' },
|
||||
] as const
|
||||
|
||||
export const COMPUTE_LABELS: Record<string, string> = Object.fromEntries(
|
||||
COMPUTE_OPTIONS.map((o) => [o.value, o.label])
|
||||
)
|
||||
|
||||
// Input-parameter columns (only shown in the full/raw table).
|
||||
export const THROUGHPUT_PARAM_HEADINGS = ['Filters', 'RLS', 'Connected clients'] as const
|
||||
|
||||
// Result columns (shown in both the current-selection table and the raw table).
|
||||
export const THROUGHPUT_METRIC_HEADINGS = [
|
||||
'Total DB changes /sec',
|
||||
'Max messages per client /sec',
|
||||
'Max total messages /sec',
|
||||
'Latency p95',
|
||||
] as const
|
||||
|
||||
export const THROUGHPUT_TABLE_HEADINGS = [
|
||||
...THROUGHPUT_PARAM_HEADINGS,
|
||||
...THROUGHPUT_METRIC_HEADINGS,
|
||||
] as const
|
||||
@@ -14,6 +14,13 @@ import {
|
||||
SelectValue,
|
||||
} from 'ui'
|
||||
|
||||
import {
|
||||
COMPUTE_LABELS,
|
||||
COMPUTE_OPTIONS,
|
||||
THROUGHPUT_METRIC_HEADINGS,
|
||||
THROUGHPUT_TABLE_HEADINGS,
|
||||
} from './RealtimeLimitsEstimator.constants'
|
||||
|
||||
export default function RealtimeLimitsEstimater({}) {
|
||||
const findTableValue = ({ computeAddOn, filters, rls, concurrency }) => {
|
||||
return throughputTable.find(
|
||||
@@ -71,9 +78,11 @@ export default function RealtimeLimitsEstimater({}) {
|
||||
<SelectValue className="font-mono" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="micro">Micro</SelectItem>
|
||||
<SelectItem value="small">Small to medium</SelectItem>
|
||||
<SelectItem value="large">Large to 16XL</SelectItem>
|
||||
{COMPUTE_OPTIONS.map((option) => (
|
||||
<SelectItem key={option.value} value={option.value}>
|
||||
{option.label}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
@@ -129,10 +138,11 @@ export default function RealtimeLimitsEstimater({}) {
|
||||
<table className="table-auto">
|
||||
<thead>
|
||||
<tr>
|
||||
<th className="px-4 py-2">Total DB changes /sec</th>
|
||||
<th className="px-4 py-2">Max messages per client /sec</th>
|
||||
<th className="px-4 py-2">Max total messages /sec</th>
|
||||
<th className="px-4 py-2">Latency p95</th>
|
||||
{THROUGHPUT_METRIC_HEADINGS.map((heading) => (
|
||||
<th key={heading} className="px-4 py-2">
|
||||
{heading}
|
||||
</th>
|
||||
))}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@@ -174,23 +184,15 @@ export default function RealtimeLimitsEstimater({}) {
|
||||
.filter((v, i, a) => a.indexOf(v) === i)
|
||||
.map((computeAddOn) => (
|
||||
<div>
|
||||
<h4>
|
||||
{computeAddOn === 'micro'
|
||||
? 'Micro'
|
||||
: computeAddOn === 'small'
|
||||
? 'Small to medium'
|
||||
: 'Large to 16XL'}
|
||||
</h4>
|
||||
<h4>{COMPUTE_LABELS[computeAddOn]}</h4>
|
||||
<table className="table-auto">
|
||||
<thead>
|
||||
<tr>
|
||||
<th className="px-4 py-2">Filters</th>
|
||||
<th className="px-4 py-2">RLS</th>
|
||||
<th className="px-4 py-2">Connected clients</th>
|
||||
<th className="px-4 py-2">Total DB changes /sec</th>
|
||||
<th className="px-4 py-2">Max messages per client /sec</th>
|
||||
<th className="px-4 py-2">Max total messages /sec</th>
|
||||
<th className="px-4 py-2">Latency p95</th>
|
||||
{THROUGHPUT_TABLE_HEADINGS.map((heading) => (
|
||||
<th key={heading} className="px-4 py-2">
|
||||
{heading}
|
||||
</th>
|
||||
))}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { at } from 'lodash-es'
|
||||
import { ReactNode } from 'react'
|
||||
import { config, logConstants } from 'shared-data'
|
||||
|
||||
import { resolveSharedDataPath } from './SharedData.utils'
|
||||
|
||||
const sharedData = {
|
||||
config,
|
||||
logConstants,
|
||||
@@ -25,12 +26,10 @@ function SharedData({
|
||||
data: keyof typeof sharedData
|
||||
children: ((selectedData: (typeof sharedData)[keyof typeof sharedData]) => ReactNode) | string
|
||||
}) {
|
||||
let selectedData = sharedData[data] as any
|
||||
return typeof children === 'string'
|
||||
? ((typeof (selectedData = at(selectedData, [children])[0]) === 'object'
|
||||
? `${selectedData.value ?? ''} ${selectedData.unit ?? ''}`.trim()
|
||||
: selectedData) as unknown as ReactNode)
|
||||
: children(selectedData)
|
||||
if (typeof children === 'string') {
|
||||
return resolveSharedDataPath(sharedData[data], children) as ReactNode
|
||||
}
|
||||
return children(sharedData[data])
|
||||
}
|
||||
|
||||
export { SharedData }
|
||||
@@ -0,0 +1,19 @@
|
||||
import { at } from 'lodash-es'
|
||||
|
||||
/**
|
||||
* Resolves a dot/bracket path within a shared-data dataset. If the resolved
|
||||
* value is an object with `value`/`unit` fields, returns `${value} ${unit}`
|
||||
* (trimmed); otherwise returns the resolved primitive as-is.
|
||||
*
|
||||
* Pure: no `shared-data` import. Callers supply the dataset so this util can
|
||||
* be reused by the React `<SharedData>` component (Next.js bundle) and by the
|
||||
* build-time markdown-schema handler (tsx) without each having to navigate
|
||||
* `shared-data`'s ESM/CJS interop independently.
|
||||
*/
|
||||
export function resolveSharedDataPath(dataset: unknown, path: string): string | number | undefined {
|
||||
const selected = at(dataset as any, [path])[0]
|
||||
if (typeof selected === 'object' && selected !== null) {
|
||||
return `${(selected as any).value ?? ''} ${(selected as any).unit ?? ''}`.trim()
|
||||
}
|
||||
return selected
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
<Admonition type="note">
|
||||
|
||||
This default takes effect for new projects from July 9, 2026.
|
||||
|
||||
</Admonition>
|
||||
@@ -1,283 +0,0 @@
|
||||
# Official error codes for Supabase Auth
|
||||
#
|
||||
# Error codes should be documented in the following format
|
||||
#
|
||||
# [error_code]
|
||||
# description = "Error description."
|
||||
# resolution = "How to resolve this error."
|
||||
# [[error_code.references]]
|
||||
# href = "https://supabase.com/docs/some/relevant/guide"
|
||||
# description = "Guide for doing some relevant thing"
|
||||
#
|
||||
# error_code should be a unique and stable identifier for the error, that the
|
||||
# developer can match against for error handling.
|
||||
|
||||
[anonymous_provider_disabled]
|
||||
description = "Anonymous sign-ins are disabled."
|
||||
|
||||
[bad_code_verifier]
|
||||
description = "Returned from the PKCE flow where the provided code verifier does not match the expected one. Indicates a bug in the implementation of the client library."
|
||||
|
||||
[bad_json]
|
||||
description = "Usually used when the HTTP body of the request is not valid JSON."
|
||||
|
||||
[bad_jwt]
|
||||
description = "JWT sent in the Authorization header is not valid."
|
||||
|
||||
[bad_oauth_callback]
|
||||
description = "OAuth callback from provider to Auth does not have all the required attributes (state). Indicates an issue with the OAuth provider or client library implementation."
|
||||
|
||||
[bad_oauth_state]
|
||||
description = "OAuth state (data echoed back by the OAuth provider to Supabase Auth) is not in the correct format. Indicates an issue with the OAuth provider integration."
|
||||
|
||||
[captcha_failed]
|
||||
description = "CAPTCHA challenge could not be verified with the CAPTCHA provider. Check your CAPTCHA integration."
|
||||
|
||||
[conflict]
|
||||
description = "General database conflict, such as concurrent requests on resources that should not be modified concurrently. Can often occur when you have too many session refresh requests firing off at the same time for a user. Check your app for concurrency issues, and if detected, back off exponentially."
|
||||
|
||||
[email_address_invalid]
|
||||
description = "Example and test domains are currently not supported. Use a different email address."
|
||||
|
||||
[email_address_not_authorized]
|
||||
description = "Email sending is not allowed for this address as your project is using the default SMTP service. Emails can only be sent to members in your Supabase organization. If you want to send emails to others, set up a custom SMTP provider."
|
||||
[[email_address_not_authorized.references]]
|
||||
href = "https://supabase.com/docs/guides/auth/auth-smtp"
|
||||
description = "Setting up a custom SMTP provider"
|
||||
|
||||
[email_conflict_identity_not_deletable]
|
||||
description = "Unlinking this identity causes the user's account to change to an email address which is already used by another user account. Indicates an issue where the user has two different accounts using different primary email addresses. You may need to migrate user data to one of their accounts in this case."
|
||||
|
||||
[email_exists]
|
||||
description = "Email address already exists in the system."
|
||||
|
||||
[email_not_confirmed]
|
||||
description = "Signing in is not allowed for this user as the email address is not confirmed."
|
||||
|
||||
[email_provider_disabled]
|
||||
description = "Signups are disabled for email and password."
|
||||
|
||||
[flow_state_expired]
|
||||
description = "PKCE flow state to which the API request relates has expired. Ask the user to sign in again."
|
||||
|
||||
[flow_state_not_found]
|
||||
description = "PKCE flow state to which the API request relates no longer exists. Flow states expire after a while and are progressively cleaned up, which can cause this error. Retried requests can cause this error, as the previous request likely destroyed the flow state. Ask the user to sign in again."
|
||||
|
||||
[hook_payload_invalid_content_type]
|
||||
description = "Payload from Auth does not have a valid Content-Type header."
|
||||
|
||||
[hook_payload_over_size_limit]
|
||||
description = "Payload from Auth exceeds maximum size limit."
|
||||
|
||||
[hook_timeout]
|
||||
description = "Unable to reach hook within maximum time allocated."
|
||||
|
||||
[hook_timeout_after_retry]
|
||||
description = "Unable to reach hook after maximum number of retries."
|
||||
|
||||
[identity_already_exists]
|
||||
description = "The identity to which the API relates is already linked to a user."
|
||||
|
||||
[identity_not_found]
|
||||
description = "Identity to which the API call relates does not exist, such as when an identity is unlinked or deleted."
|
||||
|
||||
[insufficient_aal]
|
||||
description = "To call this API, the user must have a higher Authenticator Assurance Level. To resolve, ask the user to solve an MFA challenge."
|
||||
[[insufficient_aal.references]]
|
||||
href = "https://supabase.com/docs/guides/auth/auth-mfa"
|
||||
description = "MFA"
|
||||
|
||||
[invite_not_found]
|
||||
description = "Invite is expired or already used."
|
||||
|
||||
[invalid_credentials]
|
||||
description = "Login credentials or grant type not recognized."
|
||||
|
||||
[manual_linking_disabled]
|
||||
description = "Calling the supabase.auth.linkUser() and related APIs is not enabled on the Auth server."
|
||||
|
||||
[mfa_challenge_expired]
|
||||
description = "Responding to an MFA challenge should happen within a fixed time period. Request a new challenge when encountering this error."
|
||||
|
||||
[mfa_factor_name_conflict]
|
||||
description = "MFA factors for a single user should not have the same friendly name."
|
||||
|
||||
[mfa_factor_not_found]
|
||||
description = "MFA factor no longer exists."
|
||||
|
||||
[mfa_ip_address_mismatch]
|
||||
description = "The enrollment process for MFA factors must begin and end with the same IP address."
|
||||
|
||||
[mfa_phone_enroll_not_enabled]
|
||||
description = "Enrollment of MFA Phone factors is disabled."
|
||||
|
||||
[mfa_phone_verify_not_enabled]
|
||||
description = "Login via Phone factors and verification of new Phone factors is disabled."
|
||||
|
||||
[mfa_totp_enroll_not_enabled]
|
||||
description = "Enrollment of MFA TOTP factors is disabled."
|
||||
|
||||
[mfa_totp_verify_not_enabled]
|
||||
description = "Login via TOTP factors and verification of new TOTP factors is disabled."
|
||||
|
||||
[mfa_verification_failed]
|
||||
description = "MFA challenge could not be verified -- wrong TOTP code."
|
||||
|
||||
[mfa_verification_rejected]
|
||||
description = "Further MFA verification is rejected. Only returned if the MFA verification attempt hook returns a reject decision."
|
||||
[[mfa_verification_rejected.references]]
|
||||
href = "https://supabase.com/docs/guides/auth/auth-hooks/mfa-verification-hook"
|
||||
description = "MFA verification hook"
|
||||
|
||||
[mfa_verified_factor_exists]
|
||||
description = "Verified phone factor already exists for a user. Unenroll existing verified phone factor to continue."
|
||||
|
||||
[mfa_web_authn_enroll_not_enabled]
|
||||
description = "Enrollment of MFA Web Authn factors is disabled."
|
||||
|
||||
[mfa_web_authn_verify_not_enabled]
|
||||
description = "Login via WebAuthn factors and verification of new WebAuthn factors is disabled."
|
||||
|
||||
[no_authorization]
|
||||
description = "This HTTP request requires an Authorization header, which is not provided."
|
||||
|
||||
[not_admin]
|
||||
description = "User accessing the API is not admin, i.e. the JWT does not contain a role claim that identifies them as an admin of the Auth server."
|
||||
|
||||
[oauth_provider_not_supported]
|
||||
description = "Using an OAuth provider which is disabled on the Auth server."
|
||||
|
||||
[otp_disabled]
|
||||
description = "Sign in with OTPs (magic link, email OTP) is disabled. Check your server's configuration."
|
||||
|
||||
[otp_expired]
|
||||
description = "OTP code for this sign-in has expired. Ask the user to sign in again."
|
||||
|
||||
[over_email_send_rate_limit]
|
||||
description = "Too many emails have been sent to this email address. Ask the user to wait a while before trying again."
|
||||
|
||||
[over_request_rate_limit]
|
||||
description = "Too many requests have been sent by this client (IP address). Ask the user to try again in a few minutes. Sometimes can indicate a bug in your application that mistakenly sends out too many requests (such as a badly written useEffect React hook)."
|
||||
[[over_request_rate_limit.references]]
|
||||
href = "https://react.dev/reference/react/useEffect"
|
||||
description = "React useEffect hook"
|
||||
|
||||
[over_sms_send_rate_limit]
|
||||
description = "Too many SMS messages have been sent to this phone number. Ask the user to wait a while before trying again."
|
||||
|
||||
[phone_exists]
|
||||
description = "Phone number already exists in the system."
|
||||
|
||||
[phone_not_confirmed]
|
||||
description = "Signing in is not allowed for this user as the phone number is not confirmed."
|
||||
|
||||
[phone_provider_disabled]
|
||||
description = "Signups are disabled for phone and password."
|
||||
|
||||
[provider_disabled]
|
||||
description = "OAuth provider is disabled for use. Check your server's configuration."
|
||||
|
||||
[provider_email_needs_verification]
|
||||
description = "Not all OAuth providers verify their user's email address. Supabase Auth requires emails to be verified, so this error is sent out when a verification email is sent after completing the OAuth flow."
|
||||
|
||||
[reauthentication_needed]
|
||||
description = "A user needs to reauthenticate to change their password. Ask the user to reauthenticate by calling the supabase.auth.reauthenticate() API."
|
||||
|
||||
[reauthentication_not_valid]
|
||||
description = "Verifying a reauthentication failed, the code is incorrect. Ask the user to enter a new code."
|
||||
|
||||
[refresh_token_not_found]
|
||||
description = "Session containing the refresh token not found."
|
||||
|
||||
[refresh_token_already_used]
|
||||
description = "Refresh token has been revoked and falls outside the refresh token reuse interval. See the documentation on sessions for further information."
|
||||
[[refresh_token_already_used.references]]
|
||||
href = "https://supabase.com/docs/guides/auth/sessions"
|
||||
description = "Auth sessions"
|
||||
|
||||
[request_timeout]
|
||||
description = "Processing the request took too long. Retry the request."
|
||||
|
||||
[same_password]
|
||||
description = "A user that is updating their password must use a different password than the one currently used."
|
||||
|
||||
[saml_assertion_no_email]
|
||||
description = "SAML assertion (user information) was received after sign in, but no email address was found in it, which is required. Check the provider's attribute mapping and/or configuration."
|
||||
|
||||
[saml_assertion_no_user_id]
|
||||
description = "SAML assertion (user information) was received after sign in, but a user ID (called NameID) was not found in it, which is required. Check the SAML identity provider's configuration."
|
||||
|
||||
[saml_entity_id_mismatch]
|
||||
description = "(Admin API.) Updating the SAML metadata for a SAML identity provider is not possible, as the entity ID in the update does not match the entity ID in the database. This is equivalent to creating a new identity provider, and you should do that instead."
|
||||
|
||||
[saml_idp_already_exists]
|
||||
description = "(Admin API.) Adding a SAML identity provider that is already added."
|
||||
|
||||
[saml_idp_not_found]
|
||||
description = "SAML identity provider not found. Most often returned after IdP-initiated sign-in with an unregistered SAML identity provider in Supabase Auth."
|
||||
|
||||
[saml_metadata_fetch_failed]
|
||||
description = "(Admin API.) Adding or updating a SAML provider failed as its metadata could not be fetched from the provided URL."
|
||||
|
||||
[saml_provider_disabled]
|
||||
description = "Using Enterprise SSO with SAML 2.0 is not enabled on the Auth server."
|
||||
[[saml_provider_disabled.references]]
|
||||
href = "https://supabase.com/docs/guides/auth/enterprise-sso/auth-sso-saml"
|
||||
description = "Enterprise SSO"
|
||||
|
||||
[saml_relay_state_expired]
|
||||
description = "SAML relay state is an object that tracks the progress of a supabase.auth.signInWithSSO() request. The SAML identity provider should respond after a fixed amount of time, after which this error is shown. Ask the user to sign in again."
|
||||
|
||||
[saml_relay_state_not_found]
|
||||
description = "SAML relay states are progressively cleaned up after they expire, which can cause this error. Ask the user to sign in again."
|
||||
|
||||
[session_expired]
|
||||
description = "Session to which the API request relates has expired. This can occur if an inactivity timeout is configured, or the session entry has exceeded the configured timebox value. See the documentation on sessions for more information."
|
||||
[[session_expired.references]]
|
||||
href = "https://supabase.com/docs/guides/auth/sessions"
|
||||
description = "Auth sessions"
|
||||
|
||||
[session_not_found]
|
||||
description = "Session to which the API request relates no longer exists. This can occur if the user has signed out, or the session entry in the database was deleted in some other way."
|
||||
|
||||
[signup_disabled]
|
||||
description = "Sign ups (new account creation) are disabled on the server."
|
||||
|
||||
[single_identity_not_deletable]
|
||||
description = "Every user must have at least one identity attached to it, so deleting (unlinking) an identity is not allowed if it's the only one for the user."
|
||||
|
||||
[sms_send_failed]
|
||||
description = "Sending an SMS message failed. Check your SMS provider configuration."
|
||||
|
||||
[sso_domain_already_exists]
|
||||
description = "(Admin API.) Only one SSO domain can be registered per SSO identity provider."
|
||||
|
||||
[sso_provider_not_found]
|
||||
description = "SSO provider not found. Check the arguments in supabase.auth.signInWithSSO()."
|
||||
|
||||
[too_many_enrolled_mfa_factors]
|
||||
description = "A user can only have a fixed number of enrolled MFA factors."
|
||||
|
||||
[unexpected_audience]
|
||||
description = "(Deprecated feature not available via Supabase client libraries.) The request's X-JWT-AUD claim does not match the JWT's audience."
|
||||
|
||||
[unexpected_failure]
|
||||
description = "Auth service is degraded or a bug is present, without a specific reason."
|
||||
|
||||
[user_already_exists]
|
||||
description = "User with this information (email address, phone number) cannot be created again as it already exists."
|
||||
|
||||
[user_banned]
|
||||
description = "User to which the API request relates has a banned_until property which is still active. No further API requests should be attempted until this field is cleared."
|
||||
|
||||
[user_not_found]
|
||||
description = "User to which the API request relates no longer exists."
|
||||
|
||||
[user_sso_managed]
|
||||
description = "When a user comes from SSO, certain fields of the user cannot be updated (like email)."
|
||||
|
||||
[validation_failed]
|
||||
description = "Provided parameters are not in the expected format."
|
||||
|
||||
[weak_password]
|
||||
description = "User is signing up or changing their password without meeting the password strength criteria. Use the AuthWeakPasswordError class to access more information about what they need to do to make the password pass."
|
||||
@@ -1,215 +0,0 @@
|
||||
# Official error codes for Supabase Realtime
|
||||
#
|
||||
# Error codes should be documented in the following format
|
||||
#
|
||||
# [error_code]
|
||||
# description = "Error description."
|
||||
# resolution = "How to resolve this error."
|
||||
# [[error_code.references]]
|
||||
# href = "https://supabase.com/docs/some/relevant/guide"
|
||||
# description = "Guide for doing some relevant thing"
|
||||
#
|
||||
# error_code should be a unique and stable identifier for the error, that the
|
||||
# developer can match against for error handling.
|
||||
|
||||
[TopicNameRequired]
|
||||
description = "You are trying to use Realtime without a topic name set."
|
||||
|
||||
[RealtimeDisabledForConfiguration]
|
||||
description = "The configuration provided to Realtime on connect will not be able to provide you any Postgres Changes."
|
||||
resolution = "Verify your configuration on channel startup as you might not have your tables properly registered."
|
||||
|
||||
[TenantNotFound]
|
||||
description = "The tenant you are trying to connect to does not exist."
|
||||
resolution = "Verify the tenant name you are trying to connect to exists in the realtime.tenants table."
|
||||
|
||||
[ErrorConnectingToWebsocket]
|
||||
description = "Error when trying to connect to the WebSocket server."
|
||||
resolution = "Verify user information on connect."
|
||||
|
||||
[ErrorAuthorizingWebsocket]
|
||||
description = "Error when trying to authorize the WebSocket connection."
|
||||
resolution = "Verify user information on connect."
|
||||
|
||||
[TableHasSpacesInName]
|
||||
description = "The table you are trying to listen to has spaces in its name which we are unable to support."
|
||||
resolution = "Change the table name to not have spaces in it."
|
||||
|
||||
[UnableToDeleteTenant]
|
||||
description = "Error when trying to delete a tenant."
|
||||
|
||||
[UnableToSetPolicies]
|
||||
description = "Error when setting up Authorization Policies."
|
||||
|
||||
[UnableCheckoutConnection]
|
||||
description = "Error when trying to checkout a connection from the tenant pool."
|
||||
|
||||
[UnableToSubscribeToPostgres]
|
||||
description = "Error when trying to subscribe to Postgres changes."
|
||||
|
||||
[ReconnectSubscribeToPostgres]
|
||||
description = "Postgres changes still waiting to be subscribed."
|
||||
|
||||
[ChannelRateLimitReached]
|
||||
description = "The number of channels you can create has reached its limit."
|
||||
|
||||
[ConnectionRateLimitReached]
|
||||
description = "The number of connected clients has reached its limit."
|
||||
|
||||
[ClientJoinRateLimitReached]
|
||||
description = "The rate of joins per second from your clients has reached the channel limits."
|
||||
|
||||
[RealtimeDisabledForTenant]
|
||||
description = "Realtime has been disabled for the tenant."
|
||||
resolution = "Your project may have been suspended for exceeding usage quotas. Contact support with your project reference ID and a description of your Realtime use case."
|
||||
[[RealtimeDisabledForTenant.references]]
|
||||
href = "https://supabase.com/docs/troubleshooting/realtime-project-suspended-for-exceeding-quotas"
|
||||
description = "Troubleshooting guide for suspended projects"
|
||||
|
||||
[UnableToConnectToTenantDatabase]
|
||||
description = "Realtime was not able to connect to the tenant's database."
|
||||
|
||||
[DatabaseLackOfConnections]
|
||||
description = "Realtime was not able to connect to the tenant's database due to not having enough available connections."
|
||||
resolution = "Verify your database connection limits."
|
||||
[[DatabaseLackOfConnections.references]]
|
||||
href = "https://supabase.com/docs/guides/database/connection-management"
|
||||
description = "Connection management guide"
|
||||
|
||||
[RealtimeNodeDisconnected]
|
||||
description = "Realtime is a distributed application and this means that one the system is unable to communicate with one of the distributed nodes."
|
||||
|
||||
[MigrationsFailedToRun]
|
||||
description = "Error when running the migrations against the Tenant database that are required by Realtime."
|
||||
|
||||
[StartListenAndReplicationFailed]
|
||||
description = "Error when starting the replication and listening of errors for database broadcasting."
|
||||
|
||||
[ReplicationMaxWalSendersReached]
|
||||
description = "Maximum number of WAL senders reached in tenant database."
|
||||
[[ReplicationMaxWalSendersReached.references]]
|
||||
href = "https://supabase.com/docs/guides/database/custom-postgres-config#cli-configurable-settings"
|
||||
description = "Configuring max WAL senders"
|
||||
|
||||
[MigrationCheckFailed]
|
||||
description = "Check to see if we require to run migrations fails."
|
||||
|
||||
[PartitionCreationFailed]
|
||||
description = "Error when creating partitions for realtime.messages."
|
||||
|
||||
[ErrorStartingPostgresCDCStream]
|
||||
description = "Error when starting the Postgres CDC stream which is used for Postgres Changes."
|
||||
|
||||
[UnknownDataProcessed]
|
||||
description = "An unknown data type was processed by the Realtime system."
|
||||
|
||||
[ErrorStartingPostgresCDC]
|
||||
description = "Error when starting the Postgres CDC extension which is used for Postgres Changes."
|
||||
|
||||
[ReplicationSlotBeingUsed]
|
||||
description = "The replication slot is being used by another transaction."
|
||||
|
||||
[PoolingReplicationPreparationError]
|
||||
description = "Error when preparing the replication slot."
|
||||
|
||||
[PoolingReplicationError]
|
||||
description = "Error when pooling the replication slot."
|
||||
|
||||
[SubscriptionDeletionFailed]
|
||||
description = "Error when trying to delete a subscription for postgres changes."
|
||||
|
||||
[UnableToDeletePhantomSubscriptions]
|
||||
description = "Error when trying to delete subscriptions that are no longer being used."
|
||||
|
||||
[UnableToCheckProcessesOnRemoteNode]
|
||||
description = "Error when trying to check the processes on a remote node."
|
||||
|
||||
[UnableToCreateCounter]
|
||||
description = "Error when trying to create a counter to track rate limits for a tenant."
|
||||
|
||||
[UnableToIncrementCounter]
|
||||
description = "Error when trying to increment a counter to track rate limits for a tenant."
|
||||
|
||||
[UnableToDecrementCounter]
|
||||
description = "Error when trying to decrement a counter to track rate limits for a tenant."
|
||||
|
||||
[UnableToUpdateCounter]
|
||||
description = "Error when trying to update a counter to track rate limits for a tenant."
|
||||
|
||||
[UnableToFindCounter]
|
||||
description = "Error when trying to find a counter to track rate limits for a tenant."
|
||||
|
||||
[UnhandledProcessMessage]
|
||||
description = "Unhandled message received by a Realtime process."
|
||||
|
||||
[UnableToTrackPresence]
|
||||
description = "Error when handling track presence for this socket."
|
||||
|
||||
[UnknownPresenceEvent]
|
||||
description = "Presence event type not recognized by service."
|
||||
|
||||
[IncreaseConnectionPool]
|
||||
description = "The number of connections you have set for Realtime are not enough to handle your current use case."
|
||||
|
||||
[RlsPolicyError]
|
||||
description = "Error on RLS policy used for authorization."
|
||||
|
||||
[ConnectionInitializing]
|
||||
description = "Database is initializing connection."
|
||||
|
||||
[DatabaseConnectionIssue]
|
||||
description = "Database had connection issues and connection was not able to be established."
|
||||
|
||||
[UnableToConnectToProject]
|
||||
description = "Unable to connect to Project database."
|
||||
|
||||
[InvalidJWTExpiration]
|
||||
description = "JWT exp claim value it's incorrect."
|
||||
|
||||
[JwtSignatureError]
|
||||
description = "JWT signature was not able to be validated."
|
||||
|
||||
[MalformedJWT]
|
||||
description = "Token received does not comply with the JWT format."
|
||||
|
||||
[Unauthorized]
|
||||
description = "Unauthorized access to Realtime channel."
|
||||
|
||||
[RealtimeRestarting]
|
||||
description = "Realtime is currently restarting."
|
||||
|
||||
[UnableToProcessListenPayload]
|
||||
description = "Payload sent in NOTIFY operation was not JSON parsable."
|
||||
|
||||
[UnableToListenToTenantDatabase]
|
||||
description = "Unable to LISTEN for notifications against the Tenant Database."
|
||||
|
||||
[UnprocessableEntity]
|
||||
description = "Received a HTTP request with a body that was not able to be processed by the endpoint."
|
||||
|
||||
[InitializingProjectConnection]
|
||||
description = "Connection against Tenant database is still starting."
|
||||
|
||||
[TimeoutOnRpcCall]
|
||||
description = "RPC request within the Realtime server has timed out."
|
||||
|
||||
[ErrorOnRpcCall]
|
||||
description = "Error when calling another realtime node."
|
||||
|
||||
[ErrorExecutingTransaction]
|
||||
description = "Error executing a database transaction in tenant database."
|
||||
|
||||
[SynInitializationError]
|
||||
description = "Our framework to syncronize processes has failed to properly startup a connection to the database."
|
||||
|
||||
[JanitorFailedToDeleteOldMessages]
|
||||
description = "Scheduled task for realtime.message cleanup was unable to run."
|
||||
|
||||
[UnableToEncodeJson]
|
||||
description = "An error were we are not handling correctly the response to be sent to the end user."
|
||||
|
||||
[UnknownErrorOnController]
|
||||
description = "An error we are not handling correctly was triggered on a controller."
|
||||
|
||||
[UnknownErrorOnChannel]
|
||||
description = "An error we are not handling correctly was triggered on a channel."
|
||||
@@ -12,16 +12,25 @@ The phone messaging configuration for MFA is shared with [phone auth login](/doc
|
||||
|
||||
Below is a flow chart illustrating how the Enrollment and Verify APIs work in the context of MFA (Phone).
|
||||
|
||||
<Image
|
||||
alt="Diagram showing the flow of Multi-Factor authentication"
|
||||
src={{
|
||||
light: '/docs/img/guides/auth-mfa/auth-mfa-phone-flow.svg',
|
||||
dark: '/docs/img/guides/auth-mfa/auth-mfa-phone-flow.svg',
|
||||
}}
|
||||
containerClassName="max-w-[700px]"
|
||||
width={93}
|
||||
height={150}
|
||||
/>
|
||||
```mermaid
|
||||
flowchart TD
|
||||
InitS((Setup flow)) --> SAAL1[/Session is AAL1/]
|
||||
SAAL1 --> Enroll[Enroll API]
|
||||
Enroll --> ChallengeAPI[Challenge API]
|
||||
ChallengeAPI --> Scan[/Code sent to User/]
|
||||
Scan --> Enter[User: Enter code]
|
||||
Enter --> Verify[Verify API]
|
||||
Verify --> Check{{Is code correct?}}
|
||||
Check -->|Yes| AAL2[/Upgrade to AAL2/]
|
||||
AAL2 --> Done((Done))
|
||||
Check -->|No| Enter
|
||||
InitA((Login flow)) --> SignIn([User: Sign-in])
|
||||
SignIn --> AAL1[/Upgrade to AAL1/]
|
||||
AAL1 --> ListFactors[List Factors API]
|
||||
ListFactors -->|1 or more factors| OpenAuth([User: Select phone factor])
|
||||
OpenAuth --> Enter
|
||||
ListFactors -->|0 factors| Setup[[Setup flow]]
|
||||
```
|
||||
|
||||
### Add enrollment flow
|
||||
|
||||
|
||||
@@ -12,16 +12,25 @@ The use of a QR code was [initially introduced by Google Authenticator](https://
|
||||
|
||||
Below is a flow chart illustrating how the Enrollment, Challenge, and Verify APIs work in the context of MFA (TOTP).
|
||||
|
||||
<Image
|
||||
alt="Diagram showing the flow of Multi-Factor authentication"
|
||||
src={{
|
||||
light: '/docs/img/guides/auth-mfa/auth-mfa-flow.svg',
|
||||
dark: '/docs/img/guides/auth-mfa/auth-mfa-flow.svg',
|
||||
}}
|
||||
containerClassName="max-w-[700px]"
|
||||
width={111}
|
||||
height={150}
|
||||
/>
|
||||
```mermaid
|
||||
flowchart TD
|
||||
InitS((Setup flow)) --> SAAL1[/Session is AAL1/]
|
||||
SAAL1 --> Enroll[Enroll API]
|
||||
Enroll --> ShowQR[Show QR code]
|
||||
ShowQR --> Scan([User: Scan QR code in authenticator])
|
||||
Scan --> Enter([User: Enter code])
|
||||
Enter --> Verify[Challenge + Verify API]
|
||||
Verify --> Check{{Is code correct?}}
|
||||
Check -->|Yes| AAL2[/Upgrade to AAL2/]
|
||||
AAL2 --> Done((Done))
|
||||
Check -->|No| Enter
|
||||
InitA((Login flow)) --> SignIn([User: Sign-in])
|
||||
SignIn --> AAL1[/Upgrade to AAL1/]
|
||||
AAL1 --> ListFactors[List Factors API]
|
||||
ListFactors -->|1 or more factors| OpenAuth([User: Open authenticator])
|
||||
OpenAuth --> Enter
|
||||
ListFactors -->|0 factors| Setup[[Setup flow]]
|
||||
```
|
||||
|
||||
[TOTP MFA API](/docs/reference/javascript/auth-mfa-api) is free to use and is enabled on all Supabase projects by default.
|
||||
|
||||
|
||||
@@ -74,31 +74,12 @@ Key rotation and revocation are one of the most important processes for maintain
|
||||
|
||||
### Lifetime of a signing key
|
||||
|
||||
<div className="flex flex-row gap-6 items-center w-full">
|
||||
|
||||
<Image
|
||||
alt="Diagram showing the state transitions of a signing key"
|
||||
src={{
|
||||
light: '/docs/img/guides/auth-signing-keys/states.svg',
|
||||
dark: '/docs/img/guides/auth-signing-keys/states.svg',
|
||||
}}
|
||||
containerClassName="max-w-[300px] min-w-[180px]"
|
||||
width={336}
|
||||
height={766}
|
||||
/>
|
||||
|
||||
<div>
|
||||
|
||||
A newly created key starts off as standby, before being rotated into in use (becoming the current key) while the existing current key becomes previously used.
|
||||
|
||||
At any point you can move a key from the previously used or revoked states back to being a standby key, and rotate to it. This gives you the confidence to revert back to an older key if you identify problems with the rotation, such as forgetting to update a component of your application that is relying on a specific key (for example, the legacy JWT secret).
|
||||
|
||||
Each action on a key is reversible (except permanent deletion).
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
| Action | Accepted JWT signatures | Description |
|
||||
| -------------------------------------------------------------------------------- | ---------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| <span className="whitespace-nowrap!">Create a new key</span> | Current key only, new key has not created any JWTs yet. | When you initially create a key, after choosing the signing algorithm or importing a private key you already have, it starts out in the standby state. If using an asymmetric key (RSA, Elliptic Curve) its public key will be available in the discovery endpoint. Supabase Auth does not use this key to create new JWTs. |
|
||||
|
||||
@@ -46,8 +46,7 @@ select cron.schedule('permanent-cron-job-name', '30 seconds', 'CALL do_something
|
||||
size="medium"
|
||||
className="text-foreground-light mt-8 mb-6"
|
||||
>
|
||||
<div className="border-b mt-3 pb-3">
|
||||
<AccordionItem
|
||||
<AccordionItem
|
||||
header="Cron syntax"
|
||||
id="item-1"
|
||||
>
|
||||
@@ -68,7 +67,6 @@ select cron.schedule('permanent-cron-job-name', '30 seconds', 'CALL do_something
|
||||
|
||||
</AccordionItem>
|
||||
|
||||
</div>
|
||||
</Accordion>
|
||||
|
||||
<Admonition type="note">
|
||||
|
||||
@@ -315,14 +315,16 @@ Because the dedicated pooler is hosted on the same machine as your database, it
|
||||
|
||||
See the [connection method matrix](#how-to-connect-to-your-postgres-databases) at the top of this page for a quick reference, or follow the decision flow in the diagram below to choose the right option for your environment.
|
||||
|
||||
<Image
|
||||
alt="Decision tree diagram showing when to connect directly to Postgres or use a connection pooler."
|
||||
src={{
|
||||
dark: '/docs/img/guides/database/connecting-to-postgres/connection-decision-tree.svg',
|
||||
light: '/docs/img/guides/database/connecting-to-postgres/connection-decision-tree-light.svg',
|
||||
}}
|
||||
caption="Choosing between direct Postgres connections and connection pooling"
|
||||
width={291}
|
||||
height={150}
|
||||
|
||||
/>
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A[Where are you connecting from?] --> B[Persistent Backend]
|
||||
A --> C[Serverless / Edge]
|
||||
B --> D{IPv6 Supported?<br/>IPv4 Add-on?}
|
||||
B --> E{IPv4 Needed?}
|
||||
C --> H{IPv6 Supported?<br/>IPv4 Add-on?}
|
||||
C --> I{IPv4 Needed?}
|
||||
D --> F[Use Direct Connection]
|
||||
E --> G[Use Supavisor Session Mode]
|
||||
H --> J[Use Dedicated Pooler PgBouncer Pro]
|
||||
I --> K[Use Supavisor Transaction Mode]
|
||||
```
|
||||
@@ -149,6 +149,10 @@ Use the examples below with `supabase --experimental --project-ref <project-ref>
|
||||
| [wal_sender_timeout](https://www.postgresql.org/docs/current/runtime-config-replication.html#GUC-WAL-SENDER-TIMEOUT) | CLI only | No | `--config wal_sender_timeout=60s` |
|
||||
| [work_mem](https://www.postgresql.org/docs/current/runtime-config-resource.html#GUC-WORK-MEM) | CLI + SQL | No | `--config work_mem=64MB` |
|
||||
|
||||
#### Management API only parameters
|
||||
|
||||
Some Postgres settings are configurable through the [Management API](/docs/reference/api/v1-update-postgres-config) but not the CLI. These include logging settings such as `log_connections`. See [Postgres connection logging](/docs/guides/platform/postgres-connection-logging) for details.
|
||||
|
||||
#### Managing Postgres configuration with the CLI
|
||||
|
||||
To start:
|
||||
@@ -160,25 +164,25 @@ To update Postgres configurations, use the [`postgres config`](/docs/reference/c
|
||||
|
||||
```bash
|
||||
supabase --experimental \
|
||||
--project-ref <project-ref> \
|
||||
postgres-config update --config shared_buffers=250MB
|
||||
postgres-config update --config shared_buffers=250MB \
|
||||
--project-ref <project-ref>
|
||||
```
|
||||
|
||||
By default, the CLI will merge any provided config overrides with any existing ones. The `--replace-existing-overrides` flag can be used to instead force all existing overrides to be replaced with the ones being provided:
|
||||
|
||||
```bash
|
||||
supabase --experimental \
|
||||
--project-ref <project-ref> \
|
||||
postgres-config update --config max_parallel_workers=3 \
|
||||
--replace-existing-overrides
|
||||
--replace-existing-overrides \
|
||||
--project-ref <project-ref>
|
||||
```
|
||||
|
||||
To delete specific configuration overrides, use the `postgres-config delete` command:
|
||||
|
||||
```bash
|
||||
supabase --experimental \
|
||||
--project-ref <project-ref> \
|
||||
postgres-config delete --config shared_buffers,work_mem
|
||||
postgres-config delete --config shared_buffers,work_mem \
|
||||
--project-ref <project-ref>
|
||||
```
|
||||
|
||||
By default, CLI v2 (≥ 2.0.0) checks the parameter’s context and requests the correct action (reload or restart):
|
||||
|
||||
@@ -25,15 +25,15 @@ These options, called "query parameters," can be used to address specific errors
|
||||
connection_string.../postgres?KEY1=VALUE&KEY2=VALUE&KEY3=VALUE
|
||||
```
|
||||
|
||||
# Errors
|
||||
## Errors
|
||||
|
||||
{/* supa-mdx-lint-disable-next-line Rule001HeadingCase */}
|
||||
|
||||
## ... prepared statement already exists
|
||||
### Prepared statement already exists
|
||||
|
||||
Supavisor in transaction mode (port 6543) does not support [prepared statements](https://www.postgresql.org/docs/current/sql-prepare.html), which Prisma will try to create in the background.
|
||||
|
||||
### Solution: [#solution-prepared-statement-exists]
|
||||
#### Solution: [#solution-prepared-statement-exists]
|
||||
|
||||
- Add `pgbouncer=true` to the connection string. This turns off prepared statements in Prisma.
|
||||
|
||||
@@ -43,17 +43,17 @@ Supavisor in transaction mode (port 6543) does not support [prepared statements]
|
||||
|
||||
---
|
||||
|
||||
## Can't reach database server at:
|
||||
### Can't reach the database server
|
||||
|
||||
Prisma couldn't establish a connection with Postgres or Supavisor before the timeout
|
||||
Prisma couldn't establish a connection with Postgres or Supavisor before the timeout.
|
||||
|
||||
### Possible causes: [#possible-causes-cant-reach-database-server-at]
|
||||
#### Possible causes: [#possible-causes-cant-reach-database-server-at]
|
||||
|
||||
- **Database overload**: The database server is under heavy load, causing Prisma to struggle to connect.
|
||||
- **Malformed connection string**: The connection string used by Prisma is incorrect or incomplete.
|
||||
- **Transient network issues**: Temporary network problems are disrupting the connection.
|
||||
|
||||
### Solutions: [#solution-cant-reach-database-server-at]
|
||||
#### Solutions: [#solution-cant-reach-database-server-at]
|
||||
|
||||
- **Check database health**: Use the [Observability Dashboard](/dashboard/project/_/observability/database) to monitor CPU, memory, and I/O usage. If the database is overloaded, consider increasing your [compute size](/docs/guides/platform/compute-add-ons) or [optimizing your queries](/docs/guides/database/query-optimization).
|
||||
- **Verify connection string**: Double-check the connection string in your Prisma configuration to ensure it matches in your [project connect page](/dashboard/project/_?showConnect=true).
|
||||
@@ -65,17 +65,17 @@ Prisma couldn't establish a connection with Postgres or Supavisor before the tim
|
||||
|
||||
---
|
||||
|
||||
## Timed out fetching a new connection from the connection pool:
|
||||
### Timed out fetching a new connection from the connection pool
|
||||
|
||||
Prisma is unable to allocate connections to pending queries fast enough to meet demand.
|
||||
|
||||
### Possible causes: [#possible-causes-timed-out-fetching-a-new-connection]
|
||||
#### Possible causes: [#possible-causes-timed-out-fetching-a-new-connection]
|
||||
|
||||
- **Overwhelmed server**: The server hosting Prisma is under heavy load, limiting its ability to manage connections. By default, Prisma will create the default `num_cpus * 2 + 1` worth of connections. A common cause for server strain is increasing the `connection_limit` significantly past the default.
|
||||
- **Insufficient pool size**: The Supavisor pooler does not have enough connections available to quickly satisfy Prisma's requests.
|
||||
- **Slow queries**: Prisma's queries are taking too long to execute, preventing it from releasing connections for reuse.
|
||||
|
||||
### Solutions: [#solution-timed-out-fetching-a-new-connection]
|
||||
#### Solutions: [#solution-timed-out-fetching-a-new-connection]
|
||||
|
||||
- **Increase the pool timeout**: Increase the `pool_timeout` parameter in your Prisma configuration to give the pooler more time to allocate connections.
|
||||
- **Reduce the connection limit**: If you've explicitly increased the `connection_limit` parameter in your Prisma configuration, try reducing it to a more reasonable value.
|
||||
@@ -85,43 +85,43 @@ Prisma is unable to allocate connections to pending queries fast enough to meet
|
||||
|
||||
---
|
||||
|
||||
## Server has closed the connection
|
||||
### Server has closed the connection
|
||||
|
||||
According to this [GitHub Issue for Prisma](https://github.com/prisma/prisma/discussions/7389), this error may be related to large return values for queries. It may also be caused by significant database strain.
|
||||
|
||||
### Solutions: [#solution-server-has-closed-the-connection]
|
||||
#### Solutions: [#solution-server-has-closed-the-connection]
|
||||
|
||||
- **Limit row return sizes**: Try to limit the total amount of rows returned for particularly large requests.
|
||||
- **Minimize database strain**:Check the Reports Page for database strain. If there is obvious strain, consider [optimizing](/docs/guides/database/query-optimization) or increasing compute size
|
||||
|
||||
---
|
||||
|
||||
## Drift detected: Your database schema is not in sync with your migration history
|
||||
### Drift detected: Your database schema is not in sync with your migration history
|
||||
|
||||
Prisma relies on migration files to ensure your database aligns with Prisma's model. External schema changes are detected as "drift", which Prisma will try to overwrite, potentially causing data loss.
|
||||
|
||||
### Possible causes: [#possible-causes-your-database-schema-is-not-in-sync]
|
||||
#### Possible causes: [#possible-causes-your-database-schema-is-not-in-sync]
|
||||
|
||||
- **Supabase Managed Schemas**: Supabase may update managed schemas like auth and storage to introduce new features. Granting Prisma access to these schemas can lead to drift during updates.
|
||||
- **External Schema Modifications**: Your team or another tool might have modified the database schema outside of Prisma, causing drift.
|
||||
|
||||
### Solution: [#solution-your-database-schema-is-not-in-sync]
|
||||
#### Solution: [#solution-your-database-schema-is-not-in-sync]
|
||||
|
||||
- **Baselining migrations**: [baselining](https://www.prisma.io/docs/orm/prisma-migrate/workflows/baselining) re-syncs Prisma by capturing the current database schema as the starting point for future migrations.
|
||||
|
||||
---
|
||||
|
||||
## Max client connections reached
|
||||
### Max client connections reached
|
||||
|
||||
Postgres or Supavisor rejected a request for more connections
|
||||
|
||||
### Possible causes:[#possible-causes-max-client-connections-reached]
|
||||
#### Possible causes:[#possible-causes-max-client-connections-reached]
|
||||
|
||||
- **When working in transaction mode (port 6543):** The error "Max client connections reached" occurs when clients try to form more connections with the pooler than it can support.
|
||||
- **When working in session mode (port 5432):** The max amount of clients is restricted to the "Pool Size" value in the [Database Settings](/dashboard/project/_/database/settings). If the "Pool Size" is set to 15, even if the pooler can handle 200 client connections, it will still be effectively capped at 15 for each unique ["database-role+database" combination](https://github.com/orgs/supabase/discussions/21566).
|
||||
- **When working with direct connections**: Postgres is already servicing the max amount of connections
|
||||
|
||||
### Solutions [#solutions-causes-max-client-connections-reached]
|
||||
#### Solutions [#solutions-causes-max-client-connections-reached]
|
||||
|
||||
- **Transaction Mode for serverless apps**: If you are using serverless functions (Supabase Edge, Vercel, AWS Lambda), switch to transaction mode (port 6543). It handles more connections than session mode or direct connections.
|
||||
- **Reduce the number of Prisma connections**: A single client-server can establish multiple connections with a pooler. Typically, serverless setups do not need many connections. Starting with fewer, like five or three, or even just one, is often sufficient. In serverless setups, begin with `connection_limit=1`, increasing cautiously if needed to avoid maxing out connections.
|
||||
@@ -132,15 +132,15 @@ Postgres or Supavisor rejected a request for more connections
|
||||
|
||||
---
|
||||
|
||||
## Cross schema references are only allowed when the target schema is listed in the schemas property of your data-source
|
||||
### Cross schema references are only allowed when the target schema is listed in the schemas property of your data-source
|
||||
|
||||
A Prisma migration is referencing a schema it is not permitted to manage.
|
||||
|
||||
### Possible causes: [#possible-causes-cross-schema-references]
|
||||
#### Possible causes: [#possible-causes-cross-schema-references]
|
||||
|
||||
- A migration references a schema that Prisma is not permitted to manage
|
||||
|
||||
### Solutions: [#solutions-cross-schema-references]
|
||||
#### Solutions: [#solutions-cross-schema-references]
|
||||
|
||||
- Multi-schema support: If the external schema isn't Supabase managed, list the relevant schemas on the `datasource` block in your `schema.prisma` file.
|
||||
|
||||
|
||||
@@ -10,13 +10,13 @@ To ensure that queries return the expected data, RLS policies are correctly appl
|
||||
|
||||
- Secondly, you can test through the Supabase CLI, which is a more low-level approach where you write tests in SQL.
|
||||
|
||||
# Testing using the Supabase CLI
|
||||
## Testing using the Supabase CLI
|
||||
|
||||
You can use the Supabase CLI to test your database. The minimum required version of the CLI is [v1.11.4](https://github.com/supabase/cli/releases). To get started:
|
||||
|
||||
- [Install the Supabase CLI](/docs/guides/cli) on your local machine
|
||||
|
||||
## Creating a test
|
||||
### Creating a test
|
||||
|
||||
Create a tests folder inside the `supabase` folder:
|
||||
|
||||
@@ -30,7 +30,7 @@ Create a new file with the `.sql` extension which will contain the test.
|
||||
touch ./supabase/tests/database/hello_world.test.sql
|
||||
```
|
||||
|
||||
## Writing tests
|
||||
### Writing tests
|
||||
|
||||
All `sql` files use [pgTAP](/docs/guides/database/extensions/pgtap) as the test runner.
|
||||
|
||||
@@ -51,7 +51,7 @@ select * from finish();
|
||||
rollback;
|
||||
```
|
||||
|
||||
## Running tests
|
||||
### Running tests
|
||||
|
||||
To run the test, you can use:
|
||||
|
||||
@@ -69,7 +69,7 @@ Files=1, Tests=1, 1 wallclock secs ( 0.01 usr 0.00 sys + 0.04 cusr 0.02 csys
|
||||
Result: PASS
|
||||
```
|
||||
|
||||
## More resources
|
||||
### More resources
|
||||
|
||||
- [Testing RLS policies](/docs/guides/database/extensions/pgtap#testing-rls-policies)
|
||||
- [pgTAP extension](/docs/guides/database/extensions/pgtap)
|
||||
|
||||
@@ -98,6 +98,10 @@ You can use Supabase to store and process Protected Health Information (PHI). Yo
|
||||
- Enabling [Point in Time Recovery](/docs/guides/platform/backups#point-in-time-recovery) which requires at least a [small compute add-on](/docs/guides/platform/compute-add-ons).
|
||||
- Turning on [SSL Enforcement](/docs/guides/platform/ssl-enforcement).
|
||||
- Enabling [Network Restrictions](/docs/guides/platform/network-restrictions).
|
||||
- Keeping [Postgres connection logging](/docs/guides/platform/postgres-connection-logging) enabled. Supabase sets `log_connections` to off by default for new projects. Projects that need HIPAA compliance should keep connection logging on for audit trails, and the Security Advisor warns if it is disabled.
|
||||
|
||||
<$Partial path="log_connections_default_effective_date.mdx" />
|
||||
|
||||
- Complying with encryption requirements in the HIPAA Security Rule. Data is encrypted at rest and in transit by Supabase. You can consider encrypting the data at your application layer.
|
||||
- Not storing PHI in [public Storage buckets](/docs/guides/storage/buckets/fundamentals#public-buckets).
|
||||
- Not [transferring projects](/docs/guides/platform/project-transfer) to a non-HIPAA organization.
|
||||
|
||||
@@ -358,7 +358,7 @@ Since Llamafile provides an OpenAI API compatible server, you can either use it
|
||||
|
||||
```ts
|
||||
import { withSupabase } from 'npm:@supabase/server@^1'
|
||||
import OpenAI from 'https://deno.land/x/openai@v4.53.2/mod.ts'
|
||||
import OpenAI from 'jsr:@openai/openai@^6'
|
||||
|
||||
export default {
|
||||
fetch: withSupabase({ auth: 'publishable' }, async (req, ctx) => {
|
||||
|
||||
@@ -27,7 +27,7 @@ EdgeRuntime.waitUntil(asyncLongRunningTask())
|
||||
|
||||
export default {
|
||||
fetch: withSupabase({ auth: 'user' }, async (req, ctx) => {
|
||||
return new Response(...)
|
||||
return Response.json({ ok: true })
|
||||
}),
|
||||
}
|
||||
```
|
||||
@@ -42,7 +42,7 @@ export default {
|
||||
// Won't block the request, runs in background.
|
||||
EdgeRuntime.waitUntil(asyncLongRunningTask())
|
||||
|
||||
return new Response(...)
|
||||
return Response.json({ ok: true })
|
||||
}),
|
||||
}
|
||||
```
|
||||
@@ -62,7 +62,7 @@ addEventListener('beforeunload', (ev) => {
|
||||
|
||||
export default {
|
||||
fetch: withSupabase({ auth: 'user' }, async (req, ctx) => {
|
||||
return new Response(...)
|
||||
return Response.json({ ok: true })
|
||||
}),
|
||||
}
|
||||
```
|
||||
|
||||
@@ -31,7 +31,7 @@ export default {
|
||||
|
||||
return Response.json({ data })
|
||||
} catch (err) {
|
||||
return new Response(String(err?.message ?? err), { status: 500 })
|
||||
return Response.json({ error: String(err?.message ?? err) }, { status: 500 })
|
||||
}
|
||||
}),
|
||||
}
|
||||
|
||||
@@ -31,10 +31,10 @@ If your function doesn't use `withSupabase`, add the headers yourself. See the [
|
||||
|
||||
</Admonition>
|
||||
|
||||
Import `corsHeaders` from `@supabase/supabase-js/cors` to automatically get all required headers:
|
||||
Import `corsHeaders` from `npm:@supabase/supabase-js@^2/cors` to automatically get all required headers:
|
||||
|
||||
```ts index.ts
|
||||
import { corsHeaders } from '@supabase/supabase-js/cors'
|
||||
import { corsHeaders } from 'npm:@supabase/supabase-js@^2/cors'
|
||||
|
||||
console.log(`Function "browser-with-cors" up and running!`)
|
||||
|
||||
@@ -42,7 +42,7 @@ export default {
|
||||
fetch: async (req) => {
|
||||
// Handle the CORS preflight request.
|
||||
if (req.method === 'OPTIONS') {
|
||||
return new Response('ok', { headers: corsHeaders })
|
||||
return Response.json({ ok: true }, { headers: corsHeaders })
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
@@ -46,9 +46,9 @@ And add the code to the `index.ts` file:
|
||||
```ts index.ts
|
||||
// We need to mock the file system for the AWS SDK to work.
|
||||
import { prepareVirtualFile } from 'https://deno.land/x/mock_file@v1.1.2/mod.ts'
|
||||
import { BedrockRuntimeClient, InvokeModelCommand } from 'npm:@aws-sdk/client-bedrock-runtime'
|
||||
import { BedrockRuntimeClient, InvokeModelCommand } from 'npm:@aws-sdk/client-bedrock-runtime@^3'
|
||||
import { withSupabase } from 'npm:@supabase/server@^1'
|
||||
import { decode } from 'npm:base64-arraybuffer'
|
||||
import { decode } from 'npm:base64-arraybuffer@^1'
|
||||
|
||||
console.log('Hello from Amazon Bedrock!')
|
||||
|
||||
@@ -108,7 +108,7 @@ export default {
|
||||
upsert: false,
|
||||
})
|
||||
if (!upload) {
|
||||
return Response.json(uploadError)
|
||||
return Response.json({ error: uploadError?.message ?? 'Upload failed' }, { status: 500 })
|
||||
}
|
||||
const { data } = ctx.supabase.storage.from('images').getPublicUrl(upload.path!)
|
||||
return Response.json(data)
|
||||
|
||||
+7
-7
@@ -34,11 +34,11 @@ supabase functions new send-email
|
||||
Paste the following code into the `index.ts` file:
|
||||
|
||||
```tsx supabase/functions/send-email/index.ts
|
||||
import { Webhook } from 'https://esm.sh/standardwebhooks@1.0.0'
|
||||
import { renderAsync } from 'npm:@react-email/components@0.0.22'
|
||||
import { Webhook } from 'npm:standardwebhooks@^1'
|
||||
import { renderAsync } from 'npm:@react-email/components@^1'
|
||||
import { withSupabase } from 'npm:@supabase/server@^1'
|
||||
import React from 'npm:react@18.3.1'
|
||||
import { Resend } from 'npm:resend@4.0.0'
|
||||
import React from 'npm:react@^19'
|
||||
import { Resend } from 'npm:resend@^6'
|
||||
|
||||
import { MagicLinkEmail } from './_templates/magic-link.tsx'
|
||||
|
||||
@@ -48,7 +48,7 @@ const hookSecret = (Deno.env.get('SEND_EMAIL_HOOK_SECRET') as string).replace('v
|
||||
export default {
|
||||
fetch: withSupabase({ auth: 'none' }, async (req) => {
|
||||
if (req.method !== 'POST') {
|
||||
return new Response('not allowed', { status: 400 })
|
||||
return Response.json({ error: 'not allowed' }, { status: 400 })
|
||||
}
|
||||
|
||||
const payload = await req.text()
|
||||
@@ -124,8 +124,8 @@ import {
|
||||
Link,
|
||||
Preview,
|
||||
Text,
|
||||
} from 'npm:@react-email/components@0.0.22'
|
||||
import * as React from 'npm:react@18.3.1'
|
||||
} from 'npm:@react-email/components@^1'
|
||||
import * as React from 'npm:react@^19'
|
||||
|
||||
interface MagicLinkEmailProps {
|
||||
supabase_url: string
|
||||
|
||||
@@ -54,9 +54,9 @@ export default {
|
||||
const outcome = await result.json()
|
||||
console.log(outcome)
|
||||
if (outcome.success) {
|
||||
return new Response('success')
|
||||
return Response.json({ success: true })
|
||||
}
|
||||
return new Response('failure')
|
||||
return Response.json({ success: false })
|
||||
}),
|
||||
}
|
||||
```
|
||||
|
||||
@@ -102,8 +102,8 @@ In your newly created `supabase/functions/text-to-speech/index.ts` file, add the
|
||||
import 'jsr:@supabase/functions-js/edge-runtime.d.ts'
|
||||
|
||||
import { withSupabase } from 'npm:@supabase/server@^1'
|
||||
import { ElevenLabsClient } from 'npm:elevenlabs@1.52.0'
|
||||
import * as hash from 'npm:object-hash'
|
||||
import { ElevenLabsClient } from 'npm:elevenlabs@^1'
|
||||
import * as hash from 'npm:object-hash@^3'
|
||||
|
||||
const client = new ElevenLabsClient({
|
||||
apiKey: Deno.env.get('ELEVENLABS_API_KEY'),
|
||||
|
||||
@@ -107,13 +107,13 @@ Since Supabase Edge Function uses the [Deno runtime](https://deno.land/), you do
|
||||
In your newly created `scribe-bot/index.ts` file, add the following code:
|
||||
|
||||
```ts supabase/functions/scribe-bot/index.ts
|
||||
import { Bot, webhookCallback } from 'https://deno.land/x/grammy@v1.34.0/mod.ts'
|
||||
import { Bot, webhookCallback } from 'npm:grammy@^1'
|
||||
|
||||
import 'jsr:@supabase/functions-js/edge-runtime.d.ts'
|
||||
|
||||
import { withSupabase } from 'npm:@supabase/server@^1'
|
||||
import type { SupabaseClient } from 'npm:@supabase/supabase-js@2'
|
||||
import { ElevenLabsClient } from 'npm:elevenlabs@1.50.5'
|
||||
import type { SupabaseClient } from 'npm:@supabase/supabase-js@^2'
|
||||
import { ElevenLabsClient } from 'npm:elevenlabs@^1'
|
||||
|
||||
console.log(`Function "elevenlabs-scribe-bot" up and running!`)
|
||||
|
||||
@@ -235,7 +235,7 @@ export default {
|
||||
try {
|
||||
const url = new URL(req.url)
|
||||
if (url.searchParams.get('secret') !== Deno.env.get('FUNCTION_SECRET')) {
|
||||
return new Response('not allowed', { status: 405 })
|
||||
return Response.json({ error: 'not allowed' }, { status: 405 })
|
||||
}
|
||||
|
||||
supabaseAdmin = ctx.supabaseAdmin
|
||||
|
||||
@@ -21,8 +21,8 @@ Generate Open Graph images with Deno and Supabase Edge Functions. [View on GitHu
|
||||
Create a `handler.tsx` file to construct the OG image in React:
|
||||
|
||||
```tsx handler.tsx
|
||||
import { ImageResponse } from 'https://deno.land/x/og_edge@0.0.4/mod.ts'
|
||||
import React from 'https://esm.sh/react@18.2.0'
|
||||
import { ImageResponse } from 'npm:@vercel/og@^0'
|
||||
import React from 'npm:react@^19'
|
||||
|
||||
export default function handler(req: Request) {
|
||||
return new ImageResponse(
|
||||
|
||||
@@ -164,7 +164,7 @@ Push notifications are an important part of any mobile app. They allow you to se
|
||||
|
||||
```ts supabase/functions/push/index.ts
|
||||
import { withSupabase } from 'npm:@supabase/server@^1'
|
||||
import { JWT } from 'npm:google-auth-library@9'
|
||||
import { JWT } from 'npm:google-auth-library@^10'
|
||||
import serviceAccount from '../service-account.json' with { type: 'json' }
|
||||
|
||||
interface Notification {
|
||||
|
||||
@@ -62,7 +62,7 @@ export default {
|
||||
.eq('id', id)
|
||||
if (error) console.warn(error.message)
|
||||
|
||||
return new Response('ok')
|
||||
return Response.json({ ok: true })
|
||||
}),
|
||||
}
|
||||
```
|
||||
@@ -112,7 +112,7 @@ const model = new Supabase.ai.Session('gte-small')
|
||||
export default {
|
||||
fetch: withSupabase({ auth: 'user' }, async (req, ctx) => {
|
||||
const { search } = await req.json()
|
||||
if (!search) return new Response('Please provide a search param!')
|
||||
if (!search) return Response.json({ error: 'Please provide a search param!' }, { status: 400 })
|
||||
// Generate embedding for search term.
|
||||
const embedding = await model.run(search, {
|
||||
mean_pool: true,
|
||||
@@ -128,7 +128,7 @@ export default {
|
||||
.select('content')
|
||||
.limit(3)
|
||||
if (error) {
|
||||
return Response.json(error)
|
||||
return Response.json({ error: error.message }, { status: 500 })
|
||||
}
|
||||
|
||||
return Response.json({ search, result })
|
||||
|
||||
@@ -23,12 +23,12 @@ supabase functions new sentryfied
|
||||
Handle exceptions within your function and send them to Sentry.
|
||||
|
||||
```tsx
|
||||
import * as Sentry from 'https://deno.land/x/sentry/index.mjs'
|
||||
import * as Sentry from 'npm:@sentry/deno@^8'
|
||||
import { withSupabase } from 'npm:@supabase/server@^1'
|
||||
|
||||
Sentry.init({
|
||||
// https://docs.sentry.io/product/sentry-basics/concepts/dsn-explainer/#where-to-find-your-dsn
|
||||
dsn: SENTRY_DSN,
|
||||
dsn: Deno.env.get('SENTRY_DSN'),
|
||||
defaultIntegrations: false,
|
||||
// Performance Monitoring
|
||||
tracesSampleRate: 1.0,
|
||||
@@ -55,7 +55,7 @@ export default {
|
||||
Sentry.captureException(e)
|
||||
// Flush Sentry before the running process closes
|
||||
await Sentry.flush(2000)
|
||||
return Response.json({ msg: 'error' }, { status: 500 })
|
||||
return Response.json({ error: 'Internal Server Error' }, { status: 500 })
|
||||
}
|
||||
}),
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ set SLACK_TOKEN=<xoxb-0000000000-0000000000-01010101010nacho101010>
|
||||
Here's the code of the Edge Function, you can change the response to handle the text received:
|
||||
|
||||
```ts index.ts
|
||||
import { WebClient } from 'https://deno.land/x/slack_web_api@6.7.2/mod.js'
|
||||
import { WebClient } from 'npm:@slack/web-api@^7'
|
||||
import { withSupabase } from 'npm:@supabase/server@^1'
|
||||
|
||||
const slackBotToken = Deno.env.get('SLACK_TOKEN') ?? ''
|
||||
@@ -55,7 +55,7 @@ export default {
|
||||
text: `Hello <@${user}>!`,
|
||||
thread_ts: ts,
|
||||
})
|
||||
return new Response('ok', { status: 200 })
|
||||
return Response.json({ ok: true })
|
||||
}
|
||||
} catch (error) {
|
||||
return Response.json({ error: error.message }, { status: 500 })
|
||||
|
||||
@@ -39,7 +39,7 @@ supabase functions new upstash-redis-counter
|
||||
And add the code to the `index.ts` file:
|
||||
|
||||
```ts index.ts
|
||||
import { Redis } from 'https://deno.land/x/upstash_redis@v1.19.3/mod.ts'
|
||||
import { Redis } from 'npm:@upstash/redis@^1'
|
||||
import { withSupabase } from 'npm:@supabase/server@^1'
|
||||
|
||||
console.log(`Function "upstash-redis-counter" up and running!`)
|
||||
|
||||
@@ -34,7 +34,7 @@ GET YOUR CERT FROM YOUR PROJECT DASHBOARD
|
||||
Create a `DenoPostgresDriver.ts` file to manage the connection to Postgres via [deno-postgres](https://deno-postgres.com/):
|
||||
|
||||
```ts DenoPostgresDriver.ts
|
||||
import { Pool, PoolClient } from 'https://deno.land/x/postgres@v0.17.0/mod.ts'
|
||||
import { Pool, PoolClient } from 'jsr:@db/postgres@^0'
|
||||
import {
|
||||
CompiledQuery,
|
||||
DatabaseConnection,
|
||||
@@ -42,9 +42,9 @@ import {
|
||||
PostgresCursorConstructor,
|
||||
QueryResult,
|
||||
TransactionSettings,
|
||||
} from 'https://esm.sh/kysely@0.23.4'
|
||||
import { freeze, isFunction } from 'https://esm.sh/kysely@0.23.4/dist/esm/util/object-utils.js'
|
||||
import { extendStackTrace } from 'https://esm.sh/kysely@0.23.4/dist/esm/util/stack-trace-utils.js'
|
||||
} from 'npm:kysely@^0'
|
||||
import { freeze, isFunction } from 'npm:kysely@^0/dist/esm/util/object-utils.js'
|
||||
import { extendStackTrace } from 'npm:kysely@^0/dist/esm/util/stack-trace-utils.js'
|
||||
|
||||
export interface PostgresDialectConfig {
|
||||
pool: Pool | (() => Promise<Pool>)
|
||||
@@ -190,14 +190,14 @@ class PostgresConnection implements DatabaseConnection {
|
||||
Create an `index.ts` file to execute a query on incoming requests:
|
||||
|
||||
```ts index.ts
|
||||
import { Pool } from 'https://deno.land/x/postgres@v0.17.0/mod.ts'
|
||||
import { Pool } from 'jsr:@db/postgres@^0'
|
||||
import {
|
||||
Generated,
|
||||
Kysely,
|
||||
PostgresAdapter,
|
||||
PostgresIntrospector,
|
||||
PostgresQueryCompiler,
|
||||
} from 'https://esm.sh/kysely@0.23.4'
|
||||
} from 'npm:kysely@^0'
|
||||
import { withSupabase } from 'npm:@supabase/server@^1'
|
||||
|
||||
import { PostgresDriver } from './DenoPostgresDriver.ts'
|
||||
@@ -258,23 +258,23 @@ export default {
|
||||
// Neat, it's properly typed \o/
|
||||
console.log(animals[0].created_at.getFullYear())
|
||||
|
||||
// Encode the result as pretty printed JSON
|
||||
const body = JSON.stringify(
|
||||
animals,
|
||||
(key, value) => (typeof value === 'bigint' ? value.toString() : value),
|
||||
2
|
||||
const data = animals.map((animal) =>
|
||||
Object.fromEntries(
|
||||
Object.entries(animal).map(([key, value]) => [
|
||||
key,
|
||||
typeof value === 'bigint' ? value.toString() : value,
|
||||
])
|
||||
)
|
||||
)
|
||||
|
||||
// Return the response with the correct content type header
|
||||
return new Response(body, {
|
||||
status: 200,
|
||||
return Response.json(data, {
|
||||
headers: {
|
||||
'Content-Type': 'application/json; charset=utf-8',
|
||||
},
|
||||
})
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
return new Response(String(err?.message ?? err), { status: 500 })
|
||||
return Response.json({ error: String(err?.message ?? err) }, { status: 500 })
|
||||
}
|
||||
}),
|
||||
}
|
||||
|
||||
@@ -44,13 +44,13 @@ import { withSupabase } from 'npm:@supabase/server@^1'
|
||||
export default {
|
||||
fetch: withSupabase({ auth: 'user' }, async (req, ctx) => {
|
||||
if (req.method === 'GET') {
|
||||
return new Response('Hello World!')
|
||||
return Response.json({ message: 'Hello World!' })
|
||||
}
|
||||
const { name } = await req.json()
|
||||
if (name) {
|
||||
return new Response(`Hello ${name}!`)
|
||||
return Response.json({ message: `Hello ${name}!` })
|
||||
}
|
||||
return new Response('Hello World!')
|
||||
return Response.json({ message: 'Hello World!' })
|
||||
}),
|
||||
}
|
||||
```
|
||||
@@ -60,7 +60,7 @@ export default {
|
||||
<TabPanel id="expressjs" label="Express">
|
||||
|
||||
```ts
|
||||
import express from 'npm:express@4.18.2'
|
||||
import express from 'npm:express@^5'
|
||||
|
||||
const app = express()
|
||||
app.use(express.json())
|
||||
@@ -70,12 +70,12 @@ app.use(express.json())
|
||||
const port = 3000
|
||||
|
||||
app.get('/hello-world', (req, res) => {
|
||||
res.send('Hello World!')
|
||||
res.json({ message: 'Hello World!' })
|
||||
})
|
||||
|
||||
app.post('/hello-world', (req, res) => {
|
||||
const { name } = req.body
|
||||
res.send(`Hello ${name}!`)
|
||||
res.json({ message: `Hello ${name}!` })
|
||||
})
|
||||
|
||||
app.listen(port, () => {
|
||||
@@ -88,18 +88,18 @@ app.listen(port, () => {
|
||||
<TabPanel id="oak" label="Oak">
|
||||
|
||||
```ts
|
||||
import { Application } from 'jsr:@oak/oak@15/application'
|
||||
import { Router } from 'jsr:@oak/oak@15/router'
|
||||
import { Application } from 'jsr:@oak/oak@^17/application'
|
||||
import { Router } from 'jsr:@oak/oak@^17/router'
|
||||
|
||||
const router = new Router()
|
||||
|
||||
router.get('/hello-world', (ctx) => {
|
||||
ctx.response.body = 'Hello world!'
|
||||
ctx.response.body = { message: 'Hello World!' }
|
||||
})
|
||||
|
||||
router.post('/hello-world', async (ctx) => {
|
||||
const { name } = await ctx.request.body.json()
|
||||
ctx.response.body = `Hello ${name}!`
|
||||
ctx.response.body = { message: `Hello ${name}!` }
|
||||
})
|
||||
|
||||
const app = new Application()
|
||||
@@ -114,17 +114,17 @@ app.listen({ port: 3000 })
|
||||
<TabPanel id="hono" label="Hono">
|
||||
|
||||
```ts
|
||||
import { Hono } from 'jsr:@hono/hono'
|
||||
import { Hono } from 'jsr:@hono/hono@^4'
|
||||
|
||||
const app = new Hono()
|
||||
|
||||
app.post('/hello-world', async (c) => {
|
||||
const { name } = await c.req.json()
|
||||
return new Response(`Hello ${name}!`)
|
||||
return c.json({ message: `Hello ${name}!` })
|
||||
})
|
||||
|
||||
app.get('/hello-world', (c) => {
|
||||
return new Response('Hello World!')
|
||||
return c.json({ message: 'Hello World!' })
|
||||
})
|
||||
|
||||
export default { fetch: app.fetch }
|
||||
@@ -173,15 +173,15 @@ let tasks: Task[] = []
|
||||
const router = new Map<string, (req: Request) => Promise<Response>>()
|
||||
|
||||
async function getAllTasks(): Promise<Response> {
|
||||
return new Response(JSON.stringify(tasks))
|
||||
return Response.json({ tasks })
|
||||
}
|
||||
|
||||
async function getTask(id: string): Promise<Response> {
|
||||
const task = tasks.find((t) => t.id === id)
|
||||
if (task) {
|
||||
return new Response(JSON.stringify(task))
|
||||
return Response.json({ task })
|
||||
} else {
|
||||
return new Response('Task not found', { status: 404 })
|
||||
return Response.json({ error: 'Task not found' }, { status: 404 })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -189,16 +189,17 @@ async function createTask(req: Request): Promise<Response> {
|
||||
const id = Math.random().toString(36).substring(7)
|
||||
const task = { id, name: '' }
|
||||
tasks.push(task)
|
||||
return new Response(JSON.stringify(task), { status: 201 })
|
||||
return Response.json({ task }, { status: 201 })
|
||||
}
|
||||
|
||||
async function updateTask(id: string, req: Request): Promise<Response> {
|
||||
const index = tasks.findIndex((t) => t.id === id)
|
||||
if (index !== -1) {
|
||||
tasks[index] = { ...tasks[index] }
|
||||
return new Response(JSON.stringify(tasks[index]))
|
||||
const updates = await req.json()
|
||||
tasks[index] = { ...tasks[index], ...updates }
|
||||
return Response.json({ task: tasks[index] })
|
||||
} else {
|
||||
return new Response('Task not found', { status: 404 })
|
||||
return Response.json({ error: 'Task not found' }, { status: 404 })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -206,9 +207,9 @@ async function deleteTask(id: string): Promise<Response> {
|
||||
const index = tasks.findIndex((t) => t.id === id)
|
||||
if (index !== -1) {
|
||||
tasks.splice(index, 1)
|
||||
return new Response('Task deleted successfully')
|
||||
return Response.json({ message: 'Task deleted successfully' })
|
||||
} else {
|
||||
return new Response('Task not found', { status: 404 })
|
||||
return Response.json({ error: 'Task not found' }, { status: 404 })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -234,19 +235,19 @@ export default {
|
||||
if (id) {
|
||||
return updateTask(id, req)
|
||||
} else {
|
||||
return new Response('Bad Request', { status: 400 })
|
||||
return Response.json({ error: 'Bad Request' }, { status: 400 })
|
||||
}
|
||||
case 'DELETE':
|
||||
if (id) {
|
||||
return deleteTask(id)
|
||||
} else {
|
||||
return new Response('Bad Request', { status: 400 })
|
||||
return Response.json({ error: 'Bad Request' }, { status: 400 })
|
||||
}
|
||||
default:
|
||||
return new Response('Method Not Allowed', { status: 405 })
|
||||
return Response.json({ error: 'Method Not Allowed' }, { status: 405 })
|
||||
}
|
||||
} catch (error) {
|
||||
return new Response(`Internal Server Error: ${error}`, { status: 500 })
|
||||
return Response.json({ error: `Internal Server Error: ${error}` }, { status: 500 })
|
||||
}
|
||||
}),
|
||||
}
|
||||
@@ -257,7 +258,7 @@ export default {
|
||||
<TabPanel id="expressjs" label="Express">
|
||||
|
||||
```ts
|
||||
import express from 'npm:express@4.18.2'
|
||||
import express from 'npm:express@^5'
|
||||
|
||||
const app = express()
|
||||
app.use(express.json())
|
||||
@@ -293,8 +294,8 @@ app.delete('/tasks/:id', async (req, res) => {
|
||||
<TabPanel id="oak" label="Oak">
|
||||
|
||||
```ts
|
||||
import { Application } from 'jsr:@oak/oak/application'
|
||||
import { Router } from 'jsr:@oak/oak/router'
|
||||
import { Application } from 'jsr:@oak/oak@^17/application'
|
||||
import { Router } from 'jsr:@oak/oak@^17/router'
|
||||
|
||||
const router = new Router()
|
||||
|
||||
@@ -357,7 +358,7 @@ app.listen({ port: 3000 })
|
||||
<TabPanel id="hono" label="Hono">
|
||||
|
||||
```ts
|
||||
import { Hono } from 'jsr:@hono/hono'
|
||||
import { Hono } from 'jsr:@hono/hono@^4'
|
||||
|
||||
// You can set the basePath with Hono
|
||||
const functionName = 'tasks'
|
||||
@@ -368,9 +369,9 @@ app.get('/:id', async (c) => {
|
||||
const id = c.req.param('id')
|
||||
const task = {} // Fetch task by id here
|
||||
if (task) {
|
||||
return new Response(JSON.stringify(task))
|
||||
return c.json({ task })
|
||||
} else {
|
||||
return new Response('Task not found', { status: 404 })
|
||||
return c.json({ error: 'Task not found' }, { status: 404 })
|
||||
}
|
||||
})
|
||||
|
||||
@@ -381,9 +382,9 @@ app.patch('/:id', async (c) => {
|
||||
const task = {} // Fetch task by id here
|
||||
if (task) {
|
||||
Object.assign(task, updates)
|
||||
return new Response(JSON.stringify(task))
|
||||
return c.json({ task })
|
||||
} else {
|
||||
return new Response('Task not found', { status: 404 })
|
||||
return c.json({ error: 'Task not found' }, { status: 404 })
|
||||
}
|
||||
})
|
||||
|
||||
@@ -392,9 +393,9 @@ app.delete('/:id', async (c) => {
|
||||
const task = {} // Fetch task by id here
|
||||
if (task) {
|
||||
// Delete task
|
||||
return new Response('Task deleted successfully')
|
||||
return c.json({ message: 'Task deleted successfully' })
|
||||
} else {
|
||||
return new Response('Task not found', { status: 404 })
|
||||
return c.json({ error: 'Task not found' }, { status: 404 })
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@@ -5,161 +5,199 @@ description: 'Writing Unit Tests for Edge Functions using Deno Test'
|
||||
subtitle: 'Writing Unit Tests for Edge Functions using Deno Test'
|
||||
---
|
||||
|
||||
Testing is an essential step in the development process to ensure the correctness and performance of your Edge Functions.
|
||||
Testing is an essential step in the development process to ensure the correctness, reliability, and performance of your Edge Functions. Because Edge Functions often combine HTTP handling, authentication, database access, and business logic, a good testing strategy gives you fast feedback and high confidence before deploying to production.
|
||||
|
||||
In this guide you will learn how to write:
|
||||
|
||||
- **Unit tests** for pure business logic such as pricing rules, calculations, etc.
|
||||
- **Integration tests** for the full Edge Function by mocking at the network layer
|
||||
|
||||
The examples and patterns shown here follow the same approaches used internally by Supabase's Edge Functions team.
|
||||
|
||||
Deno ships with a fast, native test runner and excellent mocking utilities in `@std/testing`. See the [official Deno testing documentation](https://docs.deno.com/runtime/manual/basics/testing/) for more background.
|
||||
|
||||
---
|
||||
|
||||
## Testing in Deno
|
||||
## The example scenario
|
||||
|
||||
Deno has a built-in test runner that you can use for testing JavaScript or TypeScript code. You can read the [official documentation](https://docs.deno.com/runtime/manual/basics/testing/) for more information and details about the available testing functions.
|
||||
You can use a realistic Edge Function called `process-ticket` that calculates the final price of a ticket based on the authenticated user's age (loaded from the `profiles` table).
|
||||
|
||||
**Business rules:**
|
||||
|
||||
- Children aged 8 and under → free (`0`)
|
||||
- Young people aged 9–17 → 20% discount
|
||||
- Adults aged 18 and over → full price
|
||||
|
||||
The function receives a JSON payload with a `price` field and returns `{ result: finalPrice }`.
|
||||
|
||||
This example demonstrates common real-world requirements:
|
||||
|
||||
- Request validation
|
||||
- Authenticated database access via `withSupabase`
|
||||
- Business rule application
|
||||
- Proper error handling
|
||||
|
||||
---
|
||||
|
||||
## Folder structure
|
||||
## Recommended project structure
|
||||
|
||||
We recommend creating your testing in a `supabase/functions/tests` directory, using the same name as the Function followed by `-test.ts`:
|
||||
|
||||
```bash
|
||||
└── supabase
|
||||
├── functions
|
||||
│ ├── function-one
|
||||
│ │ └── index.ts
|
||||
│ └── function-two
|
||||
│ │ └── index.ts
|
||||
│ └── tests
|
||||
│ └── function-one-test.ts # Tests for function-one
|
||||
│ └── function-two-test.ts # Tests for function-two
|
||||
└── config.toml
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Example
|
||||
|
||||
The following script is a good example to get started with testing your Edge Functions:
|
||||
|
||||
```typescript function-one-test.ts
|
||||
// Import required libraries and modules
|
||||
import { assert, assertEquals } from 'jsr:@std/assert@1'
|
||||
import { createClient, SupabaseClient } from 'npm:@supabase/supabase-js@2'
|
||||
|
||||
// Will load the .env file to Deno.env
|
||||
import 'jsr:@std/dotenv/load'
|
||||
|
||||
// Set up the configuration for the Supabase client
|
||||
const supabaseUrl = Deno.env.get('SUPABASE_URL') ?? ''
|
||||
const supabaseKey = Deno.env.get('SUPABASE_PUBLISHABLE_KEY') ?? ''
|
||||
const options = {
|
||||
auth: {
|
||||
autoRefreshToken: false,
|
||||
persistSession: false,
|
||||
detectSessionInUrl: false,
|
||||
},
|
||||
}
|
||||
|
||||
// Test the creation and functionality of the Supabase client
|
||||
const testClientCreation = async () => {
|
||||
var client: SupabaseClient = createClient(supabaseUrl, supabaseKey, options)
|
||||
|
||||
// Verify if the Supabase URL and key are provided
|
||||
if (!supabaseUrl) throw new Error('supabaseUrl is required.')
|
||||
if (!supabaseKey) throw new Error('supabaseKey is required.')
|
||||
|
||||
// Test a query to the database
|
||||
const { data: table_data, error: table_error } = await client
|
||||
.from('my_table')
|
||||
.select('*')
|
||||
.limit(1)
|
||||
if (table_error) {
|
||||
throw new Error('Invalid Supabase client: ' + table_error.message)
|
||||
}
|
||||
assert(table_data, 'Data should be returned from the query.')
|
||||
}
|
||||
|
||||
// Test the 'hello-world' function
|
||||
const testHelloWorld = async () => {
|
||||
var client: SupabaseClient = createClient(supabaseUrl, supabaseKey, options)
|
||||
|
||||
// Invoke the 'hello-world' function with a parameter
|
||||
const { data: func_data, error: func_error } = await client.functions.invoke('hello-world', {
|
||||
body: { name: 'bar' },
|
||||
})
|
||||
|
||||
// Check for errors from the function invocation
|
||||
if (func_error) {
|
||||
throw new Error('Invalid response: ' + func_error.message)
|
||||
}
|
||||
|
||||
// Log the response from the function
|
||||
console.log(JSON.stringify(func_data, null, 2))
|
||||
|
||||
// Assert that the function returned the expected result
|
||||
assertEquals(func_data.message, 'Hello bar!')
|
||||
}
|
||||
|
||||
// Register and run the tests
|
||||
Deno.test('Client Creation Test', testClientCreation)
|
||||
Deno.test('Hello-world Function Test', testHelloWorld)
|
||||
supabase/
|
||||
├── functions/
|
||||
│ ├── _shared/
|
||||
│ │ └── types.ts # Database types
|
||||
│ ├── process-ticket/
|
||||
│ │ ├── index.ts # Edge Function (uses withSupabase)
|
||||
│ │ └── pricing.ts # Pure business logic (co-located)
|
||||
│ └── tests/
|
||||
│ ├── utils/
|
||||
│ │ └── supabase_env.ts # Test helpers (env + JWT)
|
||||
│ └── process-ticket/
|
||||
│ ├── pricing.test.ts # Unit tests for pricing
|
||||
│ └── index.test.ts # Integration tests with fetch mocking
|
||||
├── config.toml
|
||||
└── deno.json
|
||||
```
|
||||
|
||||
This test case consists of two parts.
|
||||
|
||||
1. The first part tests the client library and verifies that the database can be connected to and returns values from a table (`my_table`).
|
||||
2. The second part tests the edge function and checks if the received value matches the expected value. Here's a brief overview of the code:
|
||||
- We import various testing functions from the Deno standard library, including `assert`, `assertExists`, and `assertEquals`.
|
||||
- We import the `createClient` and `SupabaseClient` classes from the `@supabase/supabase-js` library to interact with the Supabase client.
|
||||
- We define the necessary configuration for the Supabase client, including the Supabase URL, API key, and authentication options.
|
||||
- The `testClientCreation` function tests the creation of a Supabase client instance and queries the database for data from a table. It verifies that data is returned from the query.
|
||||
- The `testHelloWorld` function tests the "Hello-world" Edge Function by invoking it using the Supabase client's `functions.invoke` method. It checks if the response message matches the expected greeting.
|
||||
- We run the tests using the `Deno.test` function, providing a descriptive name for each test case and the corresponding test function.
|
||||
|
||||
<Admonition type="note">
|
||||
|
||||
Make sure to replace the placeholders (`supabaseUrl`, `supabaseKey`, `my_table`) with the actual values relevant to your Supabase setup.
|
||||
In this reference implementation the pricing logic lives inside the function folder
|
||||
`process-ticket/pricing.ts`. You can also move it to `_shared/` if you want to reuse it across
|
||||
multiple functions.
|
||||
|
||||
</Admonition>
|
||||
|
||||
See the [Development Environment](/docs/guides/functions/development-environment) and [Managing dependencies](/docs/guides/functions/dependencies) guides for recommended `deno.json` and editor setup.
|
||||
|
||||
---
|
||||
|
||||
## Running Edge Functions locally
|
||||
## Unit tests: Testing pure business logic
|
||||
|
||||
To locally test and debug Edge Functions, use the Supabase CLI to run Edge Functions locally:
|
||||
The pricing rules are pure functions with no side effects, so they are perfect candidates for fast, isolated unit tests.
|
||||
|
||||
1. Ensure that the Supabase server is running by executing the following command:
|
||||
### The pricing module
|
||||
|
||||
```bash
|
||||
supabase start
|
||||
```
|
||||
<$CodeSample
|
||||
path="/edge-functions/supabase/functions/unit-testing/process-ticket/pricing.ts"
|
||||
title="Testing pure business logic | Implementation"
|
||||
meta="supabase/functions/process-ticket/pricing.ts"
|
||||
language="typescript"
|
||||
/>
|
||||
|
||||
2. In your terminal, use the following command to serve the Edge Functions locally:
|
||||
### Unit tests
|
||||
|
||||
```bash
|
||||
supabase functions serve
|
||||
```
|
||||
The reference implementation uses the BDD-style API from `@std/testing/bdd`:
|
||||
|
||||
This command starts a local server that runs your Edge Functions, enabling you to test and debug them in a development environment.
|
||||
<$CodeSample
|
||||
path="/edge-functions/supabase/functions/unit-testing/tests/process-ticket/pricing.test.ts"
|
||||
title="Testing pure business logic | Unit-Test"
|
||||
meta="supabase/functions/tests/process-ticket/pricing.test.ts"
|
||||
language="typescript"
|
||||
/>
|
||||
|
||||
3. Create the environment variables file:
|
||||
Run the unit tests:
|
||||
|
||||
```bash
|
||||
# creates the file
|
||||
touch .env
|
||||
# adds the SUPABASE_URL secret
|
||||
echo "SUPABASE_URL=http://localhost:54321" >> .env
|
||||
# adds the SUPABASE_PUBLISHABLE_KEY secret
|
||||
echo "SUPABASE_PUBLISHABLE_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24iLCJleHAiOjE5ODM4MTI5OTZ9.CRXP1A7WOeoJeXxjNni43kdQwgnWNReilDMblYTn_I0" >> .env
|
||||
# Alternatively, you can open it in your editor:
|
||||
open .env
|
||||
```
|
||||
```bash
|
||||
deno test supabase/functions/tests/process-ticket/pricing.test.ts
|
||||
```
|
||||
|
||||
4. To run the tests, use the following command in your terminal:
|
||||
These tests run in milliseconds and give you immediate safety when changing discount rules.
|
||||
|
||||
```bash
|
||||
deno test --allow-all supabase/functions/tests/function-one-test.ts
|
||||
```
|
||||
---
|
||||
|
||||
## Integration tests: Testing the full Edge Function
|
||||
|
||||
The reference implementation uses a pattern: **mocking `globalThis.fetch`** to intercept the Supabase REST calls made by the Edge Function. This approach requires **zero changes** to your production code for testability.
|
||||
|
||||
### The Edge Function
|
||||
|
||||
<$CodeSample
|
||||
path="/edge-functions/supabase/functions/unit-testing/process-ticket/index.ts"
|
||||
title="Testing the full Edge Function | Implementation"
|
||||
meta="supabase/functions/process-ticket/index.ts"
|
||||
language="typescript"
|
||||
/>
|
||||
|
||||
Key points:
|
||||
|
||||
- Uses the high-level `withSupabase` helper from [`@supabase/server`](https://github.com/supabase/server)
|
||||
- Automatically provides an authenticated `ctx.supabase` client
|
||||
- Business logic is delegated to the co-located `pricing.ts`
|
||||
|
||||
### Integration test setup
|
||||
|
||||
This helper sets up a mock Supabase environment and generates valid RS256 JWTs for authenticated requests:
|
||||
|
||||
<$CodeSample
|
||||
path="/edge-functions/supabase/functions/unit-testing/tests/utils/supabase_env.ts"
|
||||
title="Testing the full Edge Function | Unit-Test Utils"
|
||||
meta="supabase/functions/tests/utils/supabase_env.ts"
|
||||
language="typescript"
|
||||
/>
|
||||
|
||||
### Full integration tests
|
||||
|
||||
<$CodeSample
|
||||
path="/edge-functions/supabase/functions/unit-testing/tests/process-ticket/index.test.ts"
|
||||
title="Testing the full Edge Function | Unit-Test"
|
||||
meta="supabase/functions/tests/process-ticket/index.test.ts"
|
||||
language="typescript"
|
||||
/>
|
||||
|
||||
Run the integration tests:
|
||||
|
||||
```bash
|
||||
deno test supabase/functions/tests/process-ticket/index.test.ts --allow-env
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Advantages of mocking approach
|
||||
|
||||
This guide uses `fetch()` mock to demonstrate the following benefits:
|
||||
|
||||
- Test the **real** Edge Function code path — no dependency injection needed in production code
|
||||
- Simulate database responses, auth failures, network errors
|
||||
- Keep your production Edge Function clean and focused
|
||||
- Still get fast, deterministic tests that don't require a running Supabase instance
|
||||
|
||||
This pattern fits great in higher-level helpers that you can control inner code, like `withSupabase`.
|
||||
|
||||
---
|
||||
|
||||
## Running all tests
|
||||
|
||||
Add to your `deno.json`:
|
||||
|
||||
<$CodeSample
|
||||
path="/edge-functions/supabase/functions/unit-testing/deno.json"
|
||||
title="deno.json file"
|
||||
meta="supabase/deno.json"
|
||||
language="json"
|
||||
lines={[[1,1], [6,-1]]}
|
||||
/>
|
||||
|
||||
Then:
|
||||
|
||||
```bash
|
||||
deno task test
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Best practices
|
||||
|
||||
- Keep pure business logic in separate modules (even if co-located with the function)
|
||||
- Use `withSupabase` + typed `Database` for clean, authenticated access
|
||||
- Prefer mocking at the `fetch` boundary for integration tests when you don't want to modify production code
|
||||
- Use `@std/testing/bdd` + `@std/testing/mock` for expressive, maintainable tests
|
||||
- Generate realistic JWTs in tests when your function relies on authenticated Supabase clients
|
||||
- Test both happy paths and error conditions (missing input, DB failures, invalid data)
|
||||
|
||||
---
|
||||
|
||||
## Resources
|
||||
|
||||
- Full guide on Testing Supabase Edge Functions on [Mansueli's tips](https://blog.mansueli.com/testing-supabase-edge-functions-with-deno-test)
|
||||
- Read the [Deno testing guide](https://docs.deno.com/runtime/manual/basics/testing/)
|
||||
- Learn more about [`withSupabase` and `@supabase/server`](/blog/introducing-supabase-server)
|
||||
- See the other Edge Functions guides: [Development Environment](/docs/guides/functions/development-environment), [Managing dependencies](/docs/guides/functions/dependencies), [Deploy to Production](/docs/guides/functions/deploy)
|
||||
@@ -5,7 +5,8 @@ description: 'How to handle WebSocket connections in Edge Functions'
|
||||
subtitle: 'Handle WebSocket connections in Edge Functions.'
|
||||
---
|
||||
|
||||
Edge Functions supports hosting WebSocket servers that can facilitate bi-directional communications with browser clients.
|
||||
Edge Functions supports hosting WebSocket servers that can facilitate bi-directional
|
||||
communications with browser clients.
|
||||
|
||||
This allows you to:
|
||||
|
||||
@@ -13,7 +14,8 @@ This allows you to:
|
||||
- Create WebSocket relay servers for external APIs
|
||||
- Establish both incoming and outgoing WebSocket connections
|
||||
|
||||
For a production-ready reconnect pattern with session persistence and replay, see [Resumable WebSockets with Edge Functions](/docs/guides/functions/examples/resumable-websockets).
|
||||
For a production-ready reconnect pattern with session persistence and replay, see
|
||||
[Resumable WebSockets with Edge Functions](/docs/guides/functions/examples/resumable-websockets).
|
||||
|
||||
---
|
||||
|
||||
@@ -36,7 +38,10 @@ export default {
|
||||
const upgrade = req.headers.get('upgrade') || ''
|
||||
|
||||
if (upgrade.toLowerCase() != 'websocket') {
|
||||
return new Response("request isn't trying to upgrade to WebSocket.", { status: 400 })
|
||||
return Response.json(
|
||||
{ error: "request isn't trying to upgrade to WebSocket." },
|
||||
{ status: 400 }
|
||||
)
|
||||
}
|
||||
|
||||
const { socket, response } = Deno.upgradeWebSocket(req)
|
||||
@@ -61,7 +66,7 @@ export default {
|
||||
|
||||
```ts
|
||||
import { createServer } from 'node:http'
|
||||
import { WebSocketServer } from 'npm:ws'
|
||||
import { WebSocketServer } from 'npm:ws@^8'
|
||||
|
||||
const server = createServer()
|
||||
// Since we manually created the HTTP server,
|
||||
@@ -105,7 +110,9 @@ server.listen(8080)
|
||||
|
||||
You can also establish an outbound WebSocket connection to another server from an Edge Function.
|
||||
|
||||
Combining it with incoming WebSocket servers, it's possible to use Edge Functions as a WebSocket proxy, for example as a [relay server](https://github.com/supabase-community/openai-realtime-console?tab=readme-ov-file#using-supabase-edge-functions-as-a-relay-server) for the [OpenAI Realtime API](https://platform.openai.com/docs/guides/realtime/overview).
|
||||
Combining it with incoming WebSocket servers, it's possible to use Edge Functions as a
|
||||
WebSocket proxy, for example as a [relay server](https://github.com/supabase-community/openai-realtime-console?tab=readme-ov-file#using-supabase-edge-functions-as-a-relay-server)
|
||||
for the [OpenAI Realtime API](https://platform.openai.com/docs/guides/realtime/overview).
|
||||
|
||||
<$CodeSample
|
||||
external={true}
|
||||
@@ -121,11 +128,17 @@ lines={[[1, 3], [5, -1]]}
|
||||
|
||||
## Authentication
|
||||
|
||||
WebSocket browser clients don't have the option to send custom headers. Because of this, Edge Functions won't be able to perform the usual authorization header check to verify the JWT.
|
||||
WebSocket browser clients don't have the option to send custom headers. Because of this,
|
||||
Edge Functions won't be able to perform the usual authorization header check to verify
|
||||
the JWT.
|
||||
|
||||
You can skip the default authorization header checks by explicitly providing `--no-verify-jwt` when serving and deploying functions.
|
||||
You can skip the default authorization header checks by explicitly providing
|
||||
`--no-verify-jwt` when serving and deploying functions.
|
||||
|
||||
To authenticate the user making WebSocket requests, you can pass the JWT in URL query params or via a custom protocol. The [`withSupabase`](/docs/guides/functions/auth) wrapper validates credentials on request headers, so it can't authenticate WebSocket clients. Verify the JWT yourself, as shown below.
|
||||
To authenticate the user making WebSocket requests, you can pass the JWT in URL query
|
||||
params or via a custom protocol. The [`withSupabase`](/docs/guides/functions/auth)
|
||||
wrapper validates credentials on request headers, so it can't authenticate WebSocket
|
||||
clients. Verify the JWT yourself, as shown below.
|
||||
|
||||
<Tabs
|
||||
scrollable
|
||||
@@ -137,7 +150,7 @@ To authenticate the user making WebSocket requests, you can pass the JWT in URL
|
||||
<TabPanel id="query" label="Using query params">
|
||||
|
||||
```ts
|
||||
import { createClient } from 'npm:@supabase/supabase-js@2'
|
||||
import { createClient } from 'npm:@supabase/supabase-js@^2'
|
||||
|
||||
const SUPABASE_SECRET_KEYS = JSON.parse(Deno.env.get('SUPABASE_SECRET_KEYS')!)
|
||||
const supabase = createClient(
|
||||
@@ -150,7 +163,10 @@ export default {
|
||||
fetch: async (req) => {
|
||||
const upgrade = req.headers.get('upgrade') || ''
|
||||
if (upgrade.toLowerCase() != 'websocket') {
|
||||
return new Response("request isn't trying to upgrade to WebSocket.", { status: 400 })
|
||||
return Response.json(
|
||||
{ error: "request isn't trying to upgrade to WebSocket." },
|
||||
{ status: 400 }
|
||||
)
|
||||
}
|
||||
|
||||
// Please be aware query params may be logged in some logging systems.
|
||||
@@ -159,19 +175,19 @@ export default {
|
||||
|
||||
if (!jwt) {
|
||||
console.error('Auth token not provided')
|
||||
return new Response('Auth token not provided', { status: 403 })
|
||||
return Response.json({ error: 'Auth token not provided' }, { status: 403 })
|
||||
}
|
||||
|
||||
const { error, data } = await supabase.auth.getUser(jwt)
|
||||
|
||||
if (error) {
|
||||
console.error(error)
|
||||
return new Response('Invalid token provided', { status: 403 })
|
||||
return Response.json({ error: 'Invalid token provided' }, { status: 403 })
|
||||
}
|
||||
|
||||
if (!data.user) {
|
||||
console.error('user is not authenticated')
|
||||
return new Response('User is not authenticated', { status: 403 })
|
||||
return Response.json({ error: 'User is not authenticated' }, { status: 403 })
|
||||
}
|
||||
|
||||
const { socket, response } = Deno.upgradeWebSocket(req)
|
||||
@@ -194,7 +210,7 @@ export default {
|
||||
<TabPanel id="protocol" label="Using custom protocol">
|
||||
|
||||
```ts
|
||||
import { createClient } from 'npm:@supabase/supabase-js@2'
|
||||
import { createClient } from 'npm:@supabase/supabase-js@^2'
|
||||
|
||||
const SUPABASE_SECRET_KEYS = JSON.parse(Deno.env.get('SUPABASE_SECRET_KEYS')!)
|
||||
const supabase = createClient(
|
||||
@@ -207,7 +223,10 @@ export default {
|
||||
fetch: async (req) => {
|
||||
const upgrade = req.headers.get('upgrade') || ''
|
||||
if (upgrade.toLowerCase() != 'websocket') {
|
||||
return new Response("request isn't trying to upgrade to WebSocket.", { status: 400 })
|
||||
return Response.json(
|
||||
{ error: "request isn't trying to upgrade to WebSocket." },
|
||||
{ status: 400 }
|
||||
)
|
||||
}
|
||||
|
||||
// Sec-WebScoket-Protocol may return multiple protocol values `jwt-TOKEN, value1, value 2`
|
||||
@@ -219,18 +238,18 @@ export default {
|
||||
|
||||
if (!jwt) {
|
||||
console.error('Auth token not provided')
|
||||
return new Response('Auth token not provided', { status: 403 })
|
||||
return Response.json({ error: 'Auth token not provided' }, { status: 403 })
|
||||
}
|
||||
|
||||
const { error, data } = await supabase.auth.getUser(jwt)
|
||||
if (error) {
|
||||
console.error(error)
|
||||
return new Response('Invalid token provided', { status: 403 })
|
||||
return Response.json({ error: 'Invalid token provided' }, { status: 403 })
|
||||
}
|
||||
|
||||
if (!data.user) {
|
||||
console.error('user is not authenticated')
|
||||
return new Response('User is not authenticated', { status: 403 })
|
||||
return Response.json({ error: 'User is not authenticated' }, { status: 403 })
|
||||
}
|
||||
|
||||
const { socket, response } = Deno.upgradeWebSocket(req)
|
||||
@@ -254,17 +273,24 @@ export default {
|
||||
|
||||
<Admonition type="caution">
|
||||
|
||||
The maximum duration is capped based on the wall-clock, CPU, and memory limits. The Function will shutdown when it reaches one of these [limits](/docs/guides/functions/limits).
|
||||
The maximum duration is capped based on the wall-clock, CPU, and memory limits. The
|
||||
Function will shutdown when it reaches one of these
|
||||
[limits](/docs/guides/functions/limits).
|
||||
|
||||
</Admonition>
|
||||
|
||||
When using WebSockets, keep in mind that the HTTP request is considered complete after `Deno.upgradeWebSocket(req)` returns the response. To prevent early worker retirement while the socket is still open, keep an unresolved `EdgeRuntime.waitUntil()` promise that resolves in `socket.onclose`.
|
||||
When using WebSockets, keep in mind that the HTTP request is considered complete after
|
||||
`Deno.upgradeWebSocket(req)` returns the response. To prevent early worker retirement
|
||||
while the socket is still open, keep an unresolved `EdgeRuntime.waitUntil()` promise
|
||||
that resolves in `socket.onclose`.
|
||||
|
||||
---
|
||||
|
||||
## Testing WebSockets locally
|
||||
|
||||
When testing Edge Functions locally with Supabase CLI, the instances are terminated automatically after a request is completed. This will prevent keeping WebSocket connections open.
|
||||
When testing Edge Functions locally with Supabase CLI, the instances are terminated
|
||||
automatically after a request is completed. This will prevent keeping WebSocket
|
||||
connections open.
|
||||
|
||||
To prevent that, you can update the `supabase/config.toml` with the following settings:
|
||||
|
||||
@@ -275,6 +301,7 @@ policy = "per_worker"
|
||||
|
||||
<Admonition type="caution">
|
||||
|
||||
When running with `per_worker` policy, Function won't auto-reload on edits. You will need to manually restart it by running `supabase functions serve`.
|
||||
When running with `per_worker` policy, Function won't auto-reload on edits. You will
|
||||
need to manually restart it by running `supabase functions serve`.
|
||||
|
||||
</Admonition>
|
||||
@@ -98,7 +98,29 @@ export default defineConfig({
|
||||
|
||||
Suppose you have a database with the following schema:
|
||||
|
||||

|
||||
```mermaid
|
||||
erDiagram
|
||||
User ||--o{ Post : createdBy
|
||||
User ||--o{ Comment : userId
|
||||
Post ||--o{ Comment : postId
|
||||
User {
|
||||
bigint id PK
|
||||
text email
|
||||
text name
|
||||
}
|
||||
Post {
|
||||
bigint id PK
|
||||
text title
|
||||
text content
|
||||
bigint createdBy FK
|
||||
}
|
||||
Comment {
|
||||
bigint id PK
|
||||
text text
|
||||
bigint userId FK
|
||||
bigint postId FK
|
||||
}
|
||||
```
|
||||
|
||||
You can use the seed script example generated by Snaplet `seed.ts` to define the values you want to generate. For example:
|
||||
|
||||
@@ -107,8 +129,8 @@ You can use the seed script example generated by Snaplet `seed.ts` to define the
|
||||
- Three `Post.comments` from three different users.
|
||||
|
||||
```ts seed.ts
|
||||
import { createSeedClient } from '@snaplet/seed'
|
||||
import { copycat } from '@snaplet/copycat'
|
||||
import { createSeedClient } from '@snaplet/seed'
|
||||
|
||||
async function main() {
|
||||
const seed = await createSeedClient({ dryRun: true })
|
||||
|
||||
@@ -97,7 +97,6 @@ Projects that want to use PITR must also use at least a Small compute add-on to
|
||||
size="medium"
|
||||
className="text-foreground-light mt-8 mb-6"
|
||||
>
|
||||
<div className="border-b mt-3 pb-3">
|
||||
<AccordionItem
|
||||
header="How PITR works"
|
||||
id="item-1"
|
||||
@@ -111,7 +110,6 @@ Projects that want to use PITR must also use at least a Small compute add-on to
|
||||
|
||||
</AccordionItem>
|
||||
|
||||
</div>
|
||||
</Accordion>
|
||||
|
||||
<Admonition type="note">
|
||||
|
||||
@@ -96,11 +96,11 @@ We currently do not support annual plans officially. However, you can do a [cred
|
||||
|
||||
#### What will happen when I exceed the Free Plan quota?
|
||||
|
||||
You will be notified when you exceed the Free Plan quota. It is important to take action at this point. If you continue to exceed the limits without reducing your usage, service restrictions will apply. To avoid service restrictions, you have two options: reduce your usage or upgrade to a paid plan. Learn more about restrictions in the [Fair Use Policy](#fair-use-policy) section.
|
||||
You will be notified when you exceed the Free Plan quota. It is important to take action at this point. If you continue to exceed the limits, service restrictions will apply. To avoid service restrictions, you can [manage your usage](/docs/guides/platform/manage-your-usage) or upgrade to a paid plan. Learn more about restrictions in the [Fair Use Policy](#fair-use-policy) section.
|
||||
|
||||
#### What will happen when I exceed the Pro Plan quota and have the spend cap on?
|
||||
|
||||
You will be notified when you exceed your Pro Plan quota. To unblock yourself, you can toggle off your spend cap in your [organization's billing settings](/dashboard/org/_/billing) to pay for over-usage beyond the Pro plans limits. If you continue to exceed the limits without reducing your usage or turning off the spend cap, restrictions will apply. Learn more about restrictions in the [Fair Use Policy](#fair-use-policy) section.
|
||||
You will be notified when you exceed your Pro Plan quota. To unblock yourself, you can toggle off your spend cap in your [organization's billing settings](/dashboard/org/_/billing) to pay for over-usage beyond the Pro plans limits. If you continue to exceed the limits without managing your usage or turning off the spend cap, restrictions will apply. Learn more about restrictions in the [Fair Use Policy](#fair-use-policy) section.
|
||||
|
||||
#### How do I scale beyond the limits of my Pro Plan?
|
||||
|
||||
|
||||
@@ -114,6 +114,14 @@ Free Plan projects enter [read-only](#read-only-mode) mode when your **database
|
||||
- [Upgrade to the Pro Plan](/dashboard/org/_/billing) to increase the database size quota. [Disable the Spend Cap](https://app.supabase.com/org/_/billing?panel=costControl) if you want your Pro instance to auto-scale beyond the 8 GB disk size limit.
|
||||
- [Disable read-only mode](#disabling-read-only-mode) and reduce your database size.
|
||||
|
||||
### Fair use database size restriction
|
||||
|
||||
Separate from the per-project read-only mode above, your organization can be placed under a [Fair Use](/docs/guides/platform/billing-faq#fair-use-policy) service restriction (requests return a `402` status code) when its database size exceeds the plan quota. This quota is evaluated **per organization**, summing the database size across all of your projects.
|
||||
|
||||
Importantly, it is based on the **average daily database size over the billing period**, not the live size. Reducing your database size does not immediately lift the restriction: the average stays elevated until enough lower-usage days accumulate, and it effectively resets when your billing cycle rolls over. This is why a project that is well under the limit today can still be restricted, as its average across the period is still over.
|
||||
|
||||
To resolve it, upgrade your plan or disable your Spend Cap to lift the restriction immediately. Otherwise, reduce your database size and wait for the new billing cycle, at which point the average restarts from your current size.
|
||||
|
||||
### Read-only mode
|
||||
|
||||
In some cases Supabase may put your database into read-only mode to prevent your database from exceeding the billing or disk limitations.
|
||||
|
||||
@@ -24,5 +24,6 @@ These include:
|
||||
- Enabling [Point in Time Recovery](/docs/guides/platform/backups#point-in-time-recovery) which requires at least a [small compute add-on](/docs/guides/platform/compute-add-ons).
|
||||
- Turning on [SSL Enforcement](/docs/guides/platform/ssl-enforcement).
|
||||
- Enabling [Network Restrictions](/docs/guides/platform/network-restrictions).
|
||||
- Keeping [Postgres connection logging](/docs/guides/platform/postgres-connection-logging) enabled.
|
||||
|
||||
Additional security checks and controls will be added as the security advisor is extended and additional security controls are made available.
|
||||
@@ -77,7 +77,7 @@ By default, Supabase Postgres use IPv6 addresses. If your system doesn't support
|
||||
|
||||
### Checking your network IPv6 support
|
||||
|
||||
You can check if your personal network is IPv6 compatible at https://test-ipv6.com.
|
||||
You can check if your personal network is IPv6 compatible at https://ipv6test.google.com/.
|
||||
|
||||
### Checking platforms for IPv6 support:
|
||||
|
||||
|
||||
@@ -63,6 +63,12 @@ Cached and uncached egress have independent quotas and independent pricing. Cach
|
||||
|
||||
Egress is charged by gigabyte. Charges apply only for usage exceeding your subscription plan's quota. This quota is called the Unified Egress Quota because it can be used across all services (Database, Auth, Storage etc.).
|
||||
|
||||
<Admonition type="note">
|
||||
|
||||
Egress accumulates over the billing cycle and resets at the start of the next cycle. Usage that has already been served cannot be reduced retroactively, so the optimizations below lower future egress only. If your organization is restricted for egress, the restriction clears at the start of the next billing cycle, or immediately if you upgrade your plan or disable your Spend Cap.
|
||||
|
||||
</Admonition>
|
||||
|
||||
### Usage on your invoice
|
||||
|
||||
Usage is shown as "Egress GB" and "Cached Egress GB" on your invoice.
|
||||
|
||||
@@ -12,6 +12,8 @@ You are charged for the total size of all assets in your buckets.
|
||||
Storage size is charged by Gigabyte-Hours (GB-Hrs). 1 GB-Hr represents the use of 1 GB of storage for 1 hour.
|
||||
For example, storing 10 GB of data for 5 hours results in 50 GB-Hrs (10 GB × 5 hours).
|
||||
|
||||
Because usage is measured in GB-Hrs, your Storage size for quota and billing is effectively the average across the billing period, not the live size. For example, storing 20 GB for the first half of the month and 0 GB for the second half averages to 10 GB. This means reducing storage late in the cycle lowers the average only gradually, so it may not immediately clear a restriction until the next billing cycle begins.
|
||||
|
||||
### Usage on your invoice
|
||||
|
||||
Usage is shown as "Storage Size GB-Hrs" on your invoice.
|
||||
|
||||
@@ -4,9 +4,9 @@ subtitle: 'Learn how to backup and restore projects using the Supabase CLI'
|
||||
breadcrumb: 'Migrations'
|
||||
---
|
||||
|
||||
# Migrating the database
|
||||
## Migrating the database
|
||||
|
||||
## Backup database using the CLI
|
||||
### Back up database using the CLI
|
||||
|
||||
<StepHikeCompact>
|
||||
<StepHikeCompact.Step step={1}>
|
||||
@@ -74,7 +74,7 @@ breadcrumb: 'Migrations'
|
||||
|
||||
</StepHikeCompact>
|
||||
|
||||
## Before you begin
|
||||
### Before you begin
|
||||
|
||||
<Accordion
|
||||
type="default"
|
||||
@@ -84,14 +84,12 @@ breadcrumb: 'Migrations'
|
||||
size="medium"
|
||||
className="text-foreground-light mt-8 mb-6"
|
||||
>
|
||||
<div className="border-b mt-3 pb-3">
|
||||
<AccordionItem header="Install Postgres and psql" id="install-postgres">
|
||||
<$Partial path="postgres_installation.mdx" />
|
||||
</AccordionItem>
|
||||
</div>
|
||||
<AccordionItem header="Install Postgres and psql" id="install-postgres">
|
||||
<$Partial path="postgres_installation.mdx" />
|
||||
</AccordionItem>
|
||||
</Accordion>
|
||||
|
||||
## Restore backup using CLI
|
||||
### Restore backup using CLI
|
||||
|
||||
<StepHikeCompact>
|
||||
<StepHikeCompact.Step step={1}>
|
||||
@@ -197,9 +195,9 @@ breadcrumb: 'Migrations'
|
||||
|
||||
</StepHikeCompact>
|
||||
|
||||
## Special considerations
|
||||
### Special considerations
|
||||
|
||||
#### Preserving migration history
|
||||
##### Preserving migration history
|
||||
|
||||
If you were using Supabase CLI for managing migrations on your old database and would like to preserve the migration history in your newly restored project, you need to insert the migration records separately using the following commands.
|
||||
|
||||
@@ -214,7 +212,7 @@ psql \
|
||||
--dbname "$NEW_DB_URL"
|
||||
```
|
||||
|
||||
#### Schema changes to `auth` and `storage`
|
||||
##### Schema changes to `auth` and `storage`
|
||||
|
||||
If you have modified the `auth` and `storage` schemas in your old project, such as adding triggers or Row Level Security(RLS) policies, you have to restore them separately. The Supabase CLI can help you diff the changes to these schemas using the following commands.
|
||||
|
||||
@@ -223,13 +221,13 @@ supabase link --project-ref "$OLD_PROJECT_REF"
|
||||
supabase db diff --linked --schema auth,storage > changes.sql
|
||||
```
|
||||
|
||||
## Troubleshooting notes
|
||||
### Troubleshooting notes
|
||||
|
||||
#### Disabling triggers during restore:
|
||||
##### Disabling triggers during restore:
|
||||
|
||||
Setting `session_replication_role` to `replica` disables triggers during the migration, preventing columns from being double encrypted.
|
||||
|
||||
#### Custom roles require passwords
|
||||
##### Custom roles require passwords
|
||||
|
||||
If you created any [custom roles](/dashboard/project/_/database/roles) with the `LOGIN` attribute, you must manually set their passwords in the new project. This can be done with the SQL command:
|
||||
|
||||
@@ -237,7 +235,7 @@ If you created any [custom roles](/dashboard/project/_/database/roles) with the
|
||||
alter user "YOUR_USER" with password 'SOME_NEW_PASSWORD';
|
||||
```
|
||||
|
||||
#### `supabase_admin` permission errors
|
||||
##### `supabase_admin` permission errors
|
||||
|
||||
If you encounter permission errors related to `supabase_admin` during restore:
|
||||
|
||||
@@ -248,7 +246,7 @@ If you encounter permission errors related to `supabase_admin` during restore:
|
||||
ALTER ... OWNER TO "supabase_admin"
|
||||
```
|
||||
|
||||
#### `cli_login_postgres` role grant error
|
||||
##### `cli_login_postgres` role grant error
|
||||
|
||||
If you encounter the error:
|
||||
|
||||
@@ -264,7 +262,7 @@ DETAIL: Only roles with the ADMIN option on role "postgres" may grant this role
|
||||
GRANT "postgres" TO "cli_login_postgres" WITH INHERIT FALSE GRANTED BY "supabase_admin";
|
||||
```
|
||||
|
||||
#### `cli_login_postgres` role issues after cloning
|
||||
##### `cli_login_postgres` role issues after cloning
|
||||
|
||||
The `cli_login_role` must be created by the `supabase_admin` role. If the migration process cloned over the role before the CLI could generate its own version, it may encounter the error:
|
||||
|
||||
@@ -279,9 +277,9 @@ To resolve the issue, drop the custom `cli_login_postgres` role. Then the CLI ca
|
||||
DROP ROLE IF EXISTS cli_login_postgres;
|
||||
```
|
||||
|
||||
# Migrating edge functions
|
||||
## Migrating edge functions
|
||||
|
||||
## Steps (using the Supabase CLI):
|
||||
### Steps (using the Supabase CLI):
|
||||
|
||||
<StepHikeCompact>
|
||||
<StepHikeCompact.Step step={1}>
|
||||
@@ -326,7 +324,7 @@ DROP ROLE IF EXISTS cli_login_postgres;
|
||||
</StepHikeCompact.Step>
|
||||
</StepHikeCompact>
|
||||
|
||||
## Steps (using the Supabase Dashboard):
|
||||
### Steps (using the Supabase Dashboard):
|
||||
|
||||
<Admonition type="note">
|
||||
|
||||
@@ -369,7 +367,7 @@ Dependencies defined through [import maps](/docs/guides/functions/dependencies#u
|
||||
</StepHikeCompact.Step>
|
||||
</StepHikeCompact>
|
||||
|
||||
# Migrating storage objects
|
||||
## Migrating storage objects
|
||||
|
||||
<StepHikeCompact>
|
||||
<StepHikeCompact.Step step={1}>
|
||||
@@ -808,6 +806,6 @@ Dependencies defined through [import maps](/docs/guides/functions/dependencies#u
|
||||
|
||||
</StepHikeCompact>
|
||||
|
||||
## Resources
|
||||
### Resources
|
||||
|
||||
- [Connecting with PSQL](/docs/guides/database/psql)
|
||||
@@ -20,17 +20,13 @@ Dashboard backups are only available for older projects that still use logical b
|
||||
size="medium"
|
||||
className="text-foreground-light mt-8 mb-6"
|
||||
>
|
||||
<div className="border-b mt-3 pb-3">
|
||||
<AccordionItem
|
||||
<AccordionItem
|
||||
header="Install Postgres and psql"
|
||||
id="install-postgres"
|
||||
>
|
||||
<$Partial path="postgres_installation.mdx" />
|
||||
</AccordionItem>
|
||||
|
||||
</div>
|
||||
<div className="border-b mt-3 pb-3">
|
||||
<AccordionItem
|
||||
<AccordionItem
|
||||
header="Create and configure a new project"
|
||||
id="create-project"
|
||||
>
|
||||
@@ -52,7 +48,6 @@ Dashboard backups are only available for older projects that still use logical b
|
||||
</StepHikeCompact>
|
||||
</AccordionItem>
|
||||
|
||||
</div>
|
||||
</Accordion>
|
||||
|
||||
## Things to keep in mind
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
---
|
||||
id: 'postgres-connection-logging'
|
||||
title: 'Postgres connection logging'
|
||||
description: 'Enable or disable Postgres connection logging for audit and compliance.'
|
||||
---
|
||||
|
||||
For security monitoring and compliance audits, Postgres can log connection lifecycle events to your project's [Postgres logs](/docs/guides/telemetry/logs#postgres), including events such as `connection received`, `connection authenticated`, and `connection authorized`.
|
||||
|
||||
## Default behavior
|
||||
|
||||
By default, Supabase sets `log_connections` to off for new projects and you must enable it first. This behavior matches common managed Postgres defaults and reduces log volume from high-frequency connection events.
|
||||
|
||||
<$Partial path="log_connections_default_effective_date.mdx" />
|
||||
|
||||
Existing projects may retain different settings depending on plan and compliance configuration:
|
||||
|
||||
- **Team, Enterprise, and HIPAA organizations** — Connection logging is typically enabled to support audit requirements.
|
||||
- **HIPAA projects** — Supabase enables connection logging when a project is marked as high compliance. The [Security Advisor](/dashboard/project/_/advisors/security) warns if connection logging is later disabled.
|
||||
|
||||
## Compliance considerations
|
||||
|
||||
<Admonition type="note">
|
||||
|
||||
If you need connection audit evidence for SOC 2 or other compliance programs, you must enable it explicitly.
|
||||
|
||||
</Admonition>
|
||||
|
||||
Connection logging supports audit and monitoring controls required by some compliance programs:
|
||||
|
||||
- **HIPAA** — High-compliance projects should keep connection logging enabled. See the [shared responsibility model for healthcare data](/docs/guides/deployment/shared-responsibility-model#managing-healthcare-data) and [HIPAA compliance guide](/docs/guides/security/hipaa-compliance).
|
||||
- **SOC 2** — Users who need connection audit evidence should enable logging and retain logs according to their own policies. See the [SOC 2 compliance guide](/docs/guides/security/soc-2-compliance).
|
||||
|
||||
Disabling connection logging does not affect other Supabase logging (for example, [Platform Audit Logs](/docs/guides/security/platform-audit-logs), [Auth Audit Logs](/docs/guides/auth/audit-logs), or [pgAudit](/docs/guides/telemetry/logs#configuring-pgauditlog)).
|
||||
|
||||
## Manage connection logging via the dashboard
|
||||
|
||||
You can configure connection logging from the **Log connections** setting in the [Database Settings](/dashboard/project/_/database/settings) section of the Dashboard.
|
||||
|
||||
Ensure that you have [Owner or Admin permissions](/docs/guides/platform/access-control#manage-team-members) for the project.
|
||||
|
||||
<Admonition type="note">
|
||||
|
||||
Connection events appear in Postgres logs. In the [Logs Explorer](/dashboard/project/_/logs-explorer), connection lifecycle messages may be hidden by default to reduce noise. Use the connection logs filter in the sidebar to show or hide them.
|
||||
|
||||
</Admonition>
|
||||
|
||||
## Manage connection logging via the Management API
|
||||
|
||||
You can also manage connection logging using the [Management API](/docs/reference/api/v1-update-postgres-config):
|
||||
|
||||
```bash
|
||||
# Get your access token from https://supabase.com/dashboard/account/tokens
|
||||
export SUPABASE_ACCESS_TOKEN="your-access-token"
|
||||
export PROJECT_REF="your-project-ref"
|
||||
|
||||
# Get current Postgres config
|
||||
curl -X GET "https://api.supabase.com/v1/projects/$PROJECT_REF/config/database/postgres" \
|
||||
-H "Authorization: Bearer $SUPABASE_ACCESS_TOKEN"
|
||||
|
||||
# Enable connection logging
|
||||
curl -X PUT "https://api.supabase.com/v1/projects/$PROJECT_REF/config/database/postgres" \
|
||||
-H "Authorization: Bearer $SUPABASE_ACCESS_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"log_connections": true
|
||||
}'
|
||||
|
||||
# Disable connection logging
|
||||
curl -X PUT "https://api.supabase.com/v1/projects/$PROJECT_REF/config/database/postgres" \
|
||||
-H "Authorization: Bearer $SUPABASE_ACCESS_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"log_connections": false
|
||||
}'
|
||||
```
|
||||
|
||||
To verify the setting, use the SQL Editor:
|
||||
|
||||
```sql
|
||||
show log_connections;
|
||||
```
|
||||
@@ -38,23 +38,31 @@ You can only read data from a Read Replica. This is in contrast to a Primary dat
|
||||
size="large"
|
||||
className="text-foreground-light mt-8 mb-6"
|
||||
>
|
||||
<div className="border-b mt-3 pb-3">
|
||||
<AccordionItem
|
||||
<AccordionItem
|
||||
header="Do you need Read Replicas?"
|
||||
id="rr-flow"
|
||||
>
|
||||
|
||||
When your database starts slowing down, you face a choice: make your existing database bigger (scale vertically), or spread the load across multiple databases (scale horizontally). Both approaches work. Neither is universally correct. The right answer depends on your workload, your budget, and where the bottleneck actually is.
|
||||
|
||||
<Image
|
||||
src="/docs/img/guides/platform/read-replicas/read-replicas-flow.svg"
|
||||
alt="Read Replicas decision flowchart"
|
||||
/>
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A[Database slowing down] --> B{CPU above 70% sustained?}
|
||||
B -->|No| C[Monitor, do not scale yet]
|
||||
B -->|Yes| D{Queries optimized? Indexes in place?}
|
||||
D -->|No| E[Run EXPLAIN ANALYZE<br/>Add missing indexes<br/>Optimize first]
|
||||
E --> D
|
||||
D -->|Yes| F{Workload 80%+ reads?}
|
||||
F -->|No| G[Upgrade compute<br/>Replicas will not help writes]
|
||||
F -->|Yes| H{Already at 16XL?}
|
||||
H -->|Yes| I[Read Replicas<br/>Only horizontal option left]
|
||||
H -->|No| J{Need workload isolation<br/>or geo-distribution?}
|
||||
J -->|Yes| K[Read Replicas]
|
||||
J -->|No| L[Either works<br/>Compute is simpler<br/>Replicas scale further]
|
||||
```
|
||||
|
||||
</AccordionItem>
|
||||
|
||||
</div>
|
||||
|
||||
</Accordion>
|
||||
|
||||
## Features
|
||||
|
||||
@@ -189,3 +189,83 @@ select graphql.resolve('{ __schema { queryType { name } } }');
|
||||
```
|
||||
|
||||
Existing projects on pg_graphql 1.5.x are not impacted unless they choose to upgrade.
|
||||
|
||||
### Ltree indexes require reindexing after upgrade
|
||||
|
||||
_Applies when upgrading to Postgres 15.18 or 17.10._
|
||||
|
||||
<Admonition type="caution">
|
||||
|
||||
You are affected only if you have indexes on `ltree` columns and your database uses a multibyte encoding or a non-`libc` collation provider.
|
||||
|
||||
</Admonition>
|
||||
|
||||
After upgrading, indexes on `ltree` columns that were built under the previous version can return incomplete results until the index is rebuilt. For example, label searches silently miss rows that are present. This affects databases using a multibyte encoding, such as UTF-8, or a non-`libc` collation provider such as ICU or builtin.
|
||||
|
||||
To mitigate this issue:
|
||||
|
||||
1. Check whether your database needs reindexing:
|
||||
|
||||
```sql
|
||||
select
|
||||
pg_encoding_to_char(encoding) as encoding,
|
||||
pg_encoding_max_length(encoding) as max_bytes_per_char, -- 1 = single-byte, >1 = multibyte
|
||||
datlocprovider as collation_provider, -- 'c' libc, 'i' icu, 'b' builtin
|
||||
(pg_encoding_max_length(encoding) > 1 or datlocprovider != 'c') as reindex_required
|
||||
from pg_database
|
||||
where datname = current_database();
|
||||
```
|
||||
|
||||
If `reindex_required` is `false`, such as a single-byte encoding like LATIN1 with `libc` collation, no action is needed.
|
||||
|
||||
2. If `reindex_required` is `true`, find the affected indexes:
|
||||
|
||||
```sql
|
||||
select schemaname, tablename, indexname
|
||||
from pg_indexes
|
||||
where
|
||||
indexname in (
|
||||
select c.relname
|
||||
from
|
||||
pg_index as i
|
||||
join pg_class as c on i.indexrelid = c.oid
|
||||
join pg_attribute as a on a.attrelid = i.indrelid and a.attnum = ANY(i.indkey)
|
||||
join pg_type as t on a.atttypid = t.oid
|
||||
where t.typname in ('ltree', '_ltree')
|
||||
);
|
||||
```
|
||||
|
||||
3. Reindex each affected index. `REINDEX INDEX CONCURRENTLY` runs online with no downtime:
|
||||
|
||||
```sql
|
||||
REINDEX INDEX CONCURRENTLY <index_name>;
|
||||
```
|
||||
|
||||
### Custom operator selectivity estimators
|
||||
|
||||
_Applies when upgrading to Postgres 15.18 or 17.10._
|
||||
|
||||
Attaching a non-built-in (extension- or user-provided) selectivity estimator function to an operator now requires superuser. Existing operators continue to work — the check only fires when an operator is (re)created, most commonly during `pg_dump` / `pg_restore`, a logical restore, or a branch.
|
||||
|
||||
Because Supabase database roles are not superusers, recreating such an operator on your behalf (for example during a restore or branch) can fail with:
|
||||
|
||||
```
|
||||
ERROR: must be superuser to specify a non-built-in restriction estimator function
|
||||
```
|
||||
|
||||
Most projects are not affected. To check whether your database has any user-defined operators that reference a non-built-in estimator:
|
||||
|
||||
```sql
|
||||
SELECT n.nspname AS schema, o.oprname AS operator
|
||||
FROM pg_operator o
|
||||
JOIN pg_namespace n ON o.oprnamespace = n.oid
|
||||
WHERE n.nspname NOT IN ('pg_catalog', 'information_schema')
|
||||
AND ((o.oprrest <> 0 AND o.oprrest::oid >= 10000)
|
||||
OR (o.oprjoin <> 0 AND o.oprjoin::oid >= 10000))
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM pg_depend d
|
||||
WHERE d.classid = 'pg_operator'::regclass AND d.objid = o.oid AND d.deptype = 'e'
|
||||
);
|
||||
```
|
||||
|
||||
If this returns no rows, your project is unaffected.
|
||||
@@ -3,7 +3,6 @@ title: Quickstart
|
||||
subtitle: 'Learn how to use Supabase Queues to add and read messages'
|
||||
---
|
||||
|
||||
{/* <!-- vale off --> */}
|
||||
This guide is an introduction to interacting with Supabase Queues via the Dashboard and official client library. Check out [Queues API Reference](/docs/guides/queues/api) for more details on our API.
|
||||
|
||||
## Concepts
|
||||
@@ -25,8 +24,6 @@ Supabase Queues offers three types of Queues:
|
||||
- **Basic Queue**: A durable Queue that stores Messages in a logged table.
|
||||
- **Unlogged Queue**: A transient Queue that stores Messages in an unlogged table for better performance but may result in loss of Queue Messages.
|
||||
|
||||
- **Partitioned Queue** (_Coming Soon_): A durable and scalable Queue that stores Messages in multiple table partitions for better performance.
|
||||
|
||||
## Create Queues
|
||||
|
||||
To get started, navigate to the [Supabase Queues](/dashboard/project/_/integrations/queues/overview) Postgres Module under Integrations in the Dashboard and enable the `pgmq` extension.
|
||||
@@ -40,8 +37,8 @@ To get started, navigate to the [Supabase Queues](/dashboard/project/_/integrati
|
||||
<Image
|
||||
alt="Supabase Dashboard Integrations page, showing the Queues Postgres Module"
|
||||
src={{
|
||||
dark: '/docs/img/queues-quickstart-install.png',
|
||||
light: '/docs/img/queues-quickstart-install.png',
|
||||
dark: '/docs/img/queues-quickstart-install-dark.png',
|
||||
light: '/docs/img/queues-quickstart-install-light.png',
|
||||
}}
|
||||
|
||||
width={2064}
|
||||
@@ -50,29 +47,24 @@ height={1720}
|
||||
|
||||
On the [Queues page](/dashboard/project/_/integrations/queues/queues):
|
||||
|
||||
- Click **Add a new queue** button
|
||||
|
||||
<Admonition type="note">
|
||||
|
||||
If you've already created a Queue click the **Create a queue** button instead.
|
||||
|
||||
</Admonition>
|
||||
- Click **Create queue** button
|
||||
|
||||
- Name your queue
|
||||
|
||||
<Admonition type="note">
|
||||
<Admonition type="tip">
|
||||
|
||||
Queue names can only be lowercase and hyphens and underscores are permitted.
|
||||
|
||||
</Admonition>
|
||||
|
||||
- Select your [Queue Type](#queue-types)
|
||||
- We recommend leaving Row Level Security (RLS) enabled. With it enabled, you don't need to set additional RLS on the queue tables.
|
||||
|
||||
<Image
|
||||
alt="Create a Queue from the Supabase Dashboard"
|
||||
alt="A screenshot showing the process to create a Queue from the Supabase Dashboard"
|
||||
src={{
|
||||
dark: '/docs/img/queues-quickstart-create.png',
|
||||
light: '/docs/img/queues-quickstart-create.png',
|
||||
dark: '/docs/img/queues-quickstart-create-dark.png',
|
||||
light: '/docs/img/queues-quickstart-create-light.png',
|
||||
}}
|
||||
|
||||
className="max-w-lg mx-auto!"
|
||||
@@ -81,51 +73,31 @@ width={1456}
|
||||
height={1420}
|
||||
/>
|
||||
|
||||
### What happens when you create a queue?
|
||||
<Admonition type="tip" title="What happens when you create a queue?">
|
||||
|
||||
Every new Queue creates two tables in the `pgmq` schema. These tables are `pgmq.q_<queue_name>` to store and process active messages and `pgmq.a_<queue_name>` to store any archived messages.
|
||||
|
||||
A "Basic Queue" will create `pgmq.q_<queue_name>` and `pgmq.a_<queue_name>` tables as logged tables.
|
||||
A "Basic Queue" creates `pgmq.q_<queue_name>` and `pgmq.a_<queue_name>` tables as logged tables.
|
||||
|
||||
However, an "Unlogged Queue" will create `pgmq.q_<queue_name>` as an unlogged table for better performance while sacrificing durability. The `pgmq.a_<queue_name>` table will still be created as a logged table so your archived messages remain safe and secure.
|
||||
However, an "Unlogged Queue" creates `pgmq.q_<queue_name>` as an unlogged table for better performance while sacrificing durability. The `pgmq.a_<queue_name>` table is still created as a logged table so your archived messages remain safe and secure.
|
||||
|
||||
</Admonition>
|
||||
|
||||
## Expose Queues to client-side consumers
|
||||
|
||||
Queues, by default, are not exposed over Supabase Data API and are only accessible via Postgres clients.
|
||||
Queues, by default, are not exposed over the Supabase Data API and are only accessible via Postgres clients.
|
||||
|
||||
However, you may grant client-side consumers access to your Queues by enabling the Supabase Data API and granting permissions to the Queues API, which is a collection of database functions in the `pgmq_public` schema that wraps the database functions in the `pgmq` schema.
|
||||
|
||||
This is to prevent direct access to the `pgmq` schema and its tables (RLS is not enabled by default on any tables) and database functions.
|
||||
|
||||
To get started, navigate to the Queues [Settings page](/dashboard/project/_/integrations/queues/settings) and toggle on “Expose Queues via PostgREST”. Once enabled, Supabase creates and exposes a `pgmq_public` schema containing database function wrappers to a subset of `pgmq`'s database functions.
|
||||
To get started, navigate to the [**Queues > Settings**](/dashboard/project/_/integrations/queues/settings) section of the Dashboard and enable **Expose Queues via PostgREST**. Once enabled, Supabase creates and exposes a `pgmq_public` schema containing database function wrappers to a subset of `pgmq`'s database functions.
|
||||
|
||||
<Image
|
||||
alt="Screenshot of Queues settings with toggle to expose to PostgREST"
|
||||
src={{
|
||||
dark: '/docs/img/queues-quickstart-settings.png',
|
||||
light: '/docs/img/queues-quickstart-settings.png',
|
||||
}}
|
||||
### Add an RLS policy on your tables in `pgmq` schema [#enable-rls-on-your-tables-in-pgmq-schema]
|
||||
|
||||
width={2140}
|
||||
height={1642}
|
||||
/>
|
||||
If you expose your pgmq schema with the Data API, for security purposes, you must enable Row Level Security (RLS) on all Queue tables (all tables in `pgmq` schema that begin with `q_`)
|
||||
|
||||
### Enable RLS on your tables in `pgmq` schema
|
||||
|
||||
For security purposes, you must enable Row Level Security (RLS) on all Queue tables (all tables in `pgmq` schema that begin with `q_`) if the Data API is enabled.
|
||||
|
||||
You’ll want to create RLS policies for any Queues you want your client-side consumers to interact with.
|
||||
|
||||
<Image
|
||||
alt="Screenshot of creating an RLS policy from the Queues settings"
|
||||
src={{
|
||||
dark: '/docs/img/queues-quickstart-rls.png',
|
||||
light: '/docs/img/queues-quickstart-rls.png',
|
||||
}}
|
||||
|
||||
width={2130}
|
||||
height={1508}
|
||||
/>
|
||||
Add an RLS policy for any Queues you want your client-side consumers to interact with, by clicking the _Add RLS Policy_ button on [the overview page of any Queue in the Dashboard](/dashboard/project/_/integrations/queues/queues).
|
||||
|
||||
### Grant permissions to `pgmq_public` database functions
|
||||
|
||||
@@ -139,13 +111,13 @@ The permissions required for each Queue API database function:
|
||||
| `read` `pop` | `Select` `Update` |
|
||||
| `archive` `delete` | `Select` `Delete` |
|
||||
|
||||
To manage your queue permissions, click on the Queue Settings button.
|
||||
To manage your queue permissions, click on the Queue Settings cog button on [the overview page of any Queue in the Dashboard](/dashboard/project/_/integrations/queues/queues).
|
||||
|
||||
<Image
|
||||
alt="Screenshot of accessing queue settings"
|
||||
alt="Screenshot highlighting the Queue Settings button on the Queues overview page in the Supabase Dashboard"
|
||||
src={{
|
||||
dark: '/docs/img/queues-quickstart-queue-settings.png',
|
||||
light: '/docs/img/queues-quickstart-queue-settings.png',
|
||||
dark: '/docs/img/queues-quickstart-queue-settings-dark.png',
|
||||
light: '/docs/img/queues-quickstart-queue-settings-light.png',
|
||||
}}
|
||||
|
||||
width={2150}
|
||||
@@ -154,26 +126,22 @@ height={1192}
|
||||
|
||||
Then enable the required roles permissions.
|
||||
|
||||
<Image
|
||||
alt="Screenshot of configuring API access for roles from the Queues settings"
|
||||
src={{
|
||||
dark: '/docs/img/queues-quickstart-roles.png',
|
||||
light: '/docs/img/queues-quickstart-roles-light.png',
|
||||
}}
|
||||
| ROLE | Select | Insert | Update | Delete |
|
||||
| ------------- | ------- | ------- | ------- | ------- |
|
||||
| anon | | | | |
|
||||
| authenticated | enabled | enabled | enabled | enabled |
|
||||
| postgres | enabled | enabled | enabled | enabled |
|
||||
| service_role | enabled | enabled | enabled | enabled |
|
||||
|
||||
width={1271}
|
||||
height={1315}
|
||||
/>
|
||||
<Admonition type="caution">
|
||||
|
||||
<Admonition type="note">
|
||||
|
||||
`postgres` and `service_role` roles should never be exposed client-side.
|
||||
You should never expose `postgres` and `service_role` roles client-side.
|
||||
|
||||
</Admonition>
|
||||
|
||||
### Enqueueing and dequeueing messages
|
||||
|
||||
Once your Queue has been created, you can begin enqueueing and dequeueing Messages.
|
||||
Once you have created your Queue, you can begin enqueueing and dequeueing Messages.
|
||||
|
||||
<Tabs
|
||||
scrollable
|
||||
|
||||
@@ -8,20 +8,20 @@ hideToc: true
|
||||
|
||||
Supabase is a hosted platform to get you started without needing to manage any infrastructure yourself. The hosted platform comes with many security and compliance controls managed by Supabase.
|
||||
|
||||
# Compliance
|
||||
## Compliance
|
||||
|
||||
Supabase is SOC 2 Type 2 compliant and regularly audited. All projects at Supabase are governed by the same set of compliance controls.
|
||||
The [SOC 2 Compliance Guide](/docs/guides/security/soc-2-compliance) explains Supabase's SOC 2 responsibilities and controls in more detail.
|
||||
|
||||
The [HIPAA Compliance Guide](/docs/guides/security/hipaa-compliance) explains Supabase's HIPAA responsibilities. Additional [security and compliance controls](/docs/guides/deployment/shared-responsibility-model#managing-healthcare-data) for projects that deal with electronic Protected Health Information (ePHI) and require HIPAA compliance are available through the HIPAA add-on.
|
||||
|
||||
# Platform configuration
|
||||
## Platform configuration
|
||||
|
||||
As a hosted platform, Supabase provides additional security controls to further enhance the security posture depending on organizations' own requirements or obligations.
|
||||
|
||||
These can be found under the [dedicated security page](/dashboard/org/_/security) under organization settings. And are described in greater detail [here](/docs/guides/security/platform-security).
|
||||
|
||||
# Product configuration
|
||||
## Product configuration
|
||||
|
||||
Each product offered by Supabase comes with customizable security controls and these security controls help ensure that applications built on Supabase are secure, compliant, and resilient against various threats.
|
||||
|
||||
|
||||
@@ -51,6 +51,12 @@ The main differentiator comes down to purpose and scope.
|
||||
|
||||
Yes. Supabase applies the same SOC 2 controls to all environments, with additional controls being applied to HIPAA environments.
|
||||
|
||||
**Does Supabase log database connections by default?**
|
||||
|
||||
No. Supabase sets Postgres `log_connections` to off by default for new projects. HIPAA and high-compliance projects should keep [connection logging](/docs/guides/platform/postgres-connection-logging) enabled. The Security Advisor warns if it is disabled.
|
||||
|
||||
<$Partial path="log_connections_default_effective_date.mdx" />
|
||||
|
||||
**How often is Supabase audited?**
|
||||
|
||||
Supabase undergoes annual audits. The HIPAA controls are audited during the same audit period as the SOC 2 controls.
|
||||
@@ -64,3 +70,4 @@ Supabase undergoes annual audits. The HIPAA controls are audited during the same
|
||||
5. [Configuring HIPAA projects](/docs/guides/platform/hipaa-projects) on Supabase
|
||||
6. [Shared Responsibility Model](/docs/guides/deployment/shared-responsibility-model)
|
||||
7. [HIPAA shared responsibility](/docs/guides/deployment/shared-responsibility-model#managing-healthcare-data)
|
||||
8. [Postgres connection logging](/docs/guides/platform/postgres-connection-logging)
|
||||
@@ -3,7 +3,9 @@ title: 'Platform Audit Logs'
|
||||
description: 'Monitor and track organization member activities via platform API or dashboard.'
|
||||
---
|
||||
|
||||
Any [Platform API](/docs/reference/api/introduction) or [dashboard](/dashboard) actions performed by organization members are logged automatically for auditing and security purposes. This includes actions such as creating a new project, inviting members, modifying an edge function or changing project settings.
|
||||
This topic covers how to view and stream Platform Audit Logs for your organization.
|
||||
|
||||
Any [Platform API](/docs/reference/api/introduction) or [dashboard](/dashboard) actions performed by organization members are logged automatically for auditing and security purposes. This includes actions such as creating a new project, inviting members, modifying an edge function or changing project settings. You can view these logs in the dashboard or stream them to an external destination using [Audit Log Drains](#accessing-audit-log-drains).
|
||||
|
||||
Besides Platform Audit Logs, Supabase Auth also provides [Auth Audit Logs](/docs/guides/auth/audit-logs) to monitor authentication-related activities within your projects.
|
||||
|
||||
@@ -42,8 +44,11 @@ For each audit log, you can see additional details by clicking on the log entry:
|
||||
|
||||
Each Supabase user account also has access to [Account Audit logs](/dashboard/account/audit) which displays these logs for only the associated user account.
|
||||
|
||||
## Accessing Audit Log Drains
|
||||
|
||||
Audit Log Drains can be configured under your [organization's audit log drains](/dashboard/org/_/audit-log-drains). For setup instructions and supported destinations, see the [Log Drains guide](/docs/guides/telemetry/log-drains).
|
||||
|
||||
## Limitations
|
||||
|
||||
- There is currently no way to export the logs via dashboard
|
||||
- There is currently no way to set up a log drain of platform audit logs
|
||||
- Retention periods depend on your plan
|
||||
@@ -23,6 +23,7 @@ Various products at Supabase have their own hardening and configuration guides,
|
||||
- [Custom claims and role based access control](/docs/guides/api/custom-claims-and-role-based-access-control-rbac)
|
||||
- [Managing Postgres roles](/docs/guides/database/postgres/roles)
|
||||
- [Managing secrets with Vault](/docs/guides/database/vault)
|
||||
- [Postgres connection logging](/docs/guides/platform/postgres-connection-logging)
|
||||
- [Superuser access and unsupported operations](docs/guides/database/postgres/roles-superuser)
|
||||
|
||||
## Storage
|
||||
|
||||
@@ -21,32 +21,35 @@ Our [HIPAA documentation](/docs/guides/security/hipaa-compliance) provides more
|
||||
|
||||
</Admonition>
|
||||
|
||||
# Meeting compliance requirements
|
||||
## Meeting compliance requirements
|
||||
|
||||
SOC 2 compliance is a critical aspect of data security for Supabase and our customers. Being fully SOC 2 compliant is a shared responsibility and here’s a breakdown of the responsibilities for both parties:
|
||||
|
||||
### Supabase responsibilities
|
||||
#### Supabase responsibilities
|
||||
|
||||
1. **Security Measures**: Supabase implements robust security controls to protect customer data. These includes measures to prevent data breaches and ensure the confidentiality and integrity of the information managed and stored by the platform. Supabase is obliged to be vigilant about security risks and must demonstrate that our security measures meet industry standards through regular audits.
|
||||
2. **Compliance Audits**: Supabase undergoes SOC 2 audits yearly to verify that our data management practices comply with the Trust Services Criteria (TSC), which include security, availability, processing integrity, confidentiality, and privacy. These audits are conducted by an independent third party.
|
||||
3. **Incident Response**: Supabase has an incident response plan in place to handle data breaches efficiently. This plan outlines how the organization detects issues, responds to incidents, and manages system vulnerabilities.
|
||||
4. **Reporting**: Upon a successful audit, Supabase receive a SOC 2 report that details our compliance status. This report is available to customers as a SOC 2 Type 2 report, and allows customers and stakeholders to assure that Supabase has implemented adequate and the requisite safeguards to protect sensitive information.
|
||||
|
||||
### Customer responsibilities
|
||||
#### Customer responsibilities
|
||||
|
||||
1. **Compliance Requirements**: Understand your own compliance requirements. While SOC 2 compliance is not a legal requirement, many enterprise customers require their providers to have a SOC 2 report. This is because it provides assurance that the provider has implemented robust controls to protect customer data.
|
||||
2. **Due Diligence**: Customers must perform due diligence when selecting Supabase as a provider. This includes reviewing the SOC 2 Type 2 report to ensure that Supabase meets the expected security standards. Customers should also understand the division of responsibilities between themselves and Supabase to avoid duplication of effort.
|
||||
3. **Monitoring and Review**: Customers should regularly monitor and review Supabase’s compliance status.
|
||||
4. **Control Compliance**: If a customer needs to be SOC 2 compliant, they should themselves implement the requisite controls and undergo a SOC 2 audit.
|
||||
5. **Audit logging**: Supabase sets [Postgres connection logging](/docs/guides/platform/postgres-connection-logging) to off by default for new projects. If your SOC 2 program requires connection audit evidence, enable connection logging and define how you retain and review those logs.
|
||||
|
||||
### Shared responsibilities
|
||||
<$Partial path="log_connections_default_effective_date.mdx" />
|
||||
|
||||
#### Shared responsibilities
|
||||
|
||||
1. **Data Security**: Both customers and Supabase share the responsibility of ensuring data security. While the Supabase, as the provider, implements the security controls, the customer must ensure that their use of the Supabase platform does not compromise these controls.
|
||||
2. **Control Compliance**: Supabase asserts through our SOC 2 that all requisite security controls are met. Customers wishing to also be SOC 2 compliant need to go through their own SOC 2 audit, verifying that security controls are met on the customer's side.
|
||||
|
||||
In summary, SOC 2 compliance involves a shared responsibility between Supabase and our customers to ensure the security and integrity of data. Supabase, as a provider, must implement and maintain robust security measures, customers must perform due diligence and monitor Supabase's compliance status, while also implement their own compliance controls to protect their sensitive information.
|
||||
|
||||
## Frequently asked questions
|
||||
### Frequently asked questions
|
||||
|
||||
**How often is Supabase SOC 2 audited?**
|
||||
|
||||
@@ -78,7 +81,8 @@ While SOC 2 itself does not mandate specific data residency requirements, organi
|
||||
SOC 2 is non-industry specific and provides a framework for the security and privacy of data. This is however not sufficient in most cases when dealing with Protected Healthcare Information (PHI), which requires additional privacy and legal controls.
|
||||
When dealing with PHI in the United States or for United States customers, HIPAA is mandatory.
|
||||
|
||||
## Resources
|
||||
### Resources
|
||||
|
||||
1. [System and Organization Controls: SOC Suite of Services](https://www.aicpa-cima.com/resources/landing/system-and-organization-controls-soc-suite-of-services)
|
||||
2. [Shared Responsibility Model](/docs/guides/deployment/shared-responsibility-model)
|
||||
3. [Postgres connection logging](/docs/guides/platform/postgres-connection-logging)
|
||||
@@ -18,7 +18,37 @@ This is important because the storage schema only stores the metadata and the ac
|
||||
|
||||
Here is the schema that represents the Storage service:
|
||||
|
||||
<img alt="Storage schema design" src="/docs/img/storage/schema-design.png" />
|
||||
```mermaid
|
||||
erDiagram
|
||||
buckets ||--o{ objects : "buckets_id:id"
|
||||
buckets {
|
||||
text id PK
|
||||
text name
|
||||
timestamptz created_at
|
||||
timestamptz updated_at
|
||||
boolean public
|
||||
bigint file_size_limit
|
||||
text[] allowed_mime_types
|
||||
text owner_id
|
||||
}
|
||||
objects {
|
||||
uuid id PK
|
||||
text bucket_id FK
|
||||
text name
|
||||
timestamptz created_at
|
||||
timestamptz updated_at
|
||||
jsonb metadata
|
||||
text[] path_tokens
|
||||
text version
|
||||
text owner_id
|
||||
}
|
||||
migrations {
|
||||
integer id PK
|
||||
varchar(100) name
|
||||
varchar(40) hash
|
||||
timestamp executed_at
|
||||
}
|
||||
```
|
||||
|
||||
You have the option to query this table directly to retrieve information about your files in Storage without the need to go through our API.
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ Log drains send all logs of the Supabase stack to one or more desired destinatio
|
||||
|
||||
You can read about the initial announcement [here](/blog/log-drains) and vote for your preferred drains in [this discussion](https://github.com/orgs/supabase/discussions/28324?sort=top).
|
||||
|
||||
# Supported destinations
|
||||
## Supported destinations
|
||||
|
||||
The following table lists the supported destinations and the required setup configuration:
|
||||
|
||||
@@ -23,7 +23,7 @@ The following table lists the supported destinations and the required setup conf
|
||||
|
||||
HTTP requests are batched with a max of 250 logs or 1 second intervals, whichever happens first. Logs are compressed via Gzip if the destination supports it.
|
||||
|
||||
## Generic HTTP endpoint
|
||||
### Generic HTTP endpoint
|
||||
|
||||
Logs are sent as a POST request with a JSON body. Both HTTP/1 and HTTP/2 protocols are supported.
|
||||
Custom headers can optionally be configured for all requests.
|
||||
@@ -138,7 +138,7 @@ Deno.serve(async (req) => {
|
||||
|
||||
</Accordion>
|
||||
|
||||
## Datadog logs
|
||||
### Datadog logs
|
||||
|
||||
Logs sent to Datadog have the name of the log source set on the `service` field of the event and the source set to `Supabase`. Logs are gzipped before they are sent to Datadog.
|
||||
|
||||
@@ -187,7 +187,7 @@ To setup Datadog log drain, generate a Datadog API key [here](https://app.datado
|
||||
|
||||
If you are interested in other log drains, upvote them [here](https://github.com/orgs/supabase/discussions/28324)
|
||||
|
||||
## Loki
|
||||
### Loki
|
||||
|
||||
Logs sent to the Loki HTTP API are specifically formatted according to the HTTP API requirements. See the official Loki HTTP API documentation for [more details](https://grafana.com/docs/loki/latest/reference/loki-http-api/#ingest-logs).
|
||||
|
||||
@@ -199,7 +199,7 @@ The `event_message` and `timestamp` fields will be dropped from the events to av
|
||||
|
||||
Loki must be configured to accept **structured metadata**, and it is advised to increase the default maximum number of structured metadata fields to at least 500 to accommodate large log event payloads of different products.
|
||||
|
||||
## Sentry
|
||||
### Sentry
|
||||
|
||||
Logs are sent to Sentry as part of [Sentry's Logging Product](https://docs.sentry.io/product/explore/logs/). Ingesting Supabase logs as Sentry errors is currently not supported.
|
||||
|
||||
@@ -213,7 +213,7 @@ All fields from the log event are attached as attributes to the Sentry log, whic
|
||||
|
||||
If you are self-hosting Sentry, Sentry Logs are only supported in self-hosted version [25.9.0](https://github.com/getsentry/self-hosted/releases/tag/25.9.0) and later.
|
||||
|
||||
## Axiom
|
||||
### Axiom
|
||||
|
||||
Logs sent to a specified Axiom's dataset as JSON of a raw log event,
|
||||
with timestamp modified to be parsed by ingestion endpoint.
|
||||
@@ -227,7 +227,7 @@ To set up the Axiom log drain, you have to:
|
||||
- API token
|
||||
4. Watch for events in the Stream panel of Axiom Console
|
||||
|
||||
## Amazon S3
|
||||
### Amazon S3
|
||||
|
||||
Logs are written to an existing S3 bucket that you own.
|
||||
|
||||
@@ -245,7 +245,7 @@ Ensure the AWS account tied to the Access Key ID has permissions to write to the
|
||||
|
||||
</Admonition>
|
||||
|
||||
## OpenTelemetry protocol (OTLP)
|
||||
### OpenTelemetry protocol (OTLP)
|
||||
|
||||
Logs are sent to any OTLP-compatible endpoint using the OpenTelemetry Protocol over HTTP with Protocol Buffers encoding.
|
||||
|
||||
@@ -352,6 +352,6 @@ Refer to your observability platform's documentation for specific authentication
|
||||
|
||||
</Accordion>
|
||||
|
||||
## Pricing
|
||||
### Pricing
|
||||
|
||||
For a detailed breakdown of how charges are calculated, refer to [Manage Log Drain usage](/docs/guides/platform/manage-your-usage/log-drains).
|
||||
@@ -143,6 +143,16 @@ Do not log Personal Identifiable Information (PII) within the `User-Agent` heade
|
||||
|
||||
</Admonition>
|
||||
|
||||
## Logging Postgres connections
|
||||
|
||||
Postgres can log connection lifecycle events to your project's Postgres logs, for example when a client connects or authenticates. By default, Supabase sets `log_connections` to off for new projects and you must enable it first.
|
||||
|
||||
<$Partial path="log_connections_default_effective_date.mdx" />
|
||||
|
||||
To enable connection logging for audit or compliance, see [Postgres connection logging](/docs/guides/platform/postgres-connection-logging).
|
||||
|
||||
In the [Logs Explorer](/dashboard/project/_/logs-explorer), connection lifecycle messages may be hidden by default. Use the connection logs filter in the sidebar to show them.
|
||||
|
||||
## Logging Postgres queries
|
||||
|
||||
To enable query logs for other categories of statements:
|
||||
|
||||
@@ -60,8 +60,7 @@ For more information, read [the Lovable Cloud FAQ](https://docs.lovable.dev/feat
|
||||
size="medium"
|
||||
className="text-foreground-light mt-8 mb-6"
|
||||
>
|
||||
<div className="border-b mt-3 pb-3">
|
||||
<AccordionItem
|
||||
<AccordionItem
|
||||
header="Can I get access to the Supabase SQL editor when using Lovable Cloud?"
|
||||
id="sql-editor-access"
|
||||
>
|
||||
@@ -70,10 +69,7 @@ For more information, read [the Lovable Cloud FAQ](https://docs.lovable.dev/feat
|
||||
|
||||
</AccordionItem>
|
||||
|
||||
</div>
|
||||
|
||||
<div className="border-b mt-3 pb-3">
|
||||
<AccordionItem
|
||||
<AccordionItem
|
||||
header="Why doesn't my Supabase project appear on my dashboard?"
|
||||
id="project-not-showing"
|
||||
>
|
||||
@@ -82,10 +78,7 @@ For more information, read [the Lovable Cloud FAQ](https://docs.lovable.dev/feat
|
||||
|
||||
</AccordionItem>
|
||||
|
||||
</div>
|
||||
|
||||
<div className="border-b mt-3 pb-3">
|
||||
<AccordionItem
|
||||
<AccordionItem
|
||||
header="Can I disconnect my project from Lovable Cloud and connect it to my own Supabase account?"
|
||||
id="disconnect-project"
|
||||
>
|
||||
@@ -96,10 +89,7 @@ For more information, read [the Lovable Cloud FAQ](https://docs.lovable.dev/feat
|
||||
|
||||
</AccordionItem>
|
||||
|
||||
</div>
|
||||
|
||||
<div className="border-b mt-3 pb-3">
|
||||
<AccordionItem
|
||||
<AccordionItem
|
||||
header="I can't get my database URL to connect from an external tool (like BI tools or Postgres connectors)."
|
||||
id="database-url"
|
||||
>
|
||||
@@ -108,10 +98,7 @@ For more information, read [the Lovable Cloud FAQ](https://docs.lovable.dev/feat
|
||||
|
||||
</AccordionItem>
|
||||
|
||||
</div>
|
||||
|
||||
<div className="border-b mt-3 pb-3">
|
||||
<AccordionItem
|
||||
<AccordionItem
|
||||
header="I can't get my service role key to integrate an external service (like n8n or Make.com)."
|
||||
id="service-role-key"
|
||||
>
|
||||
@@ -122,6 +109,4 @@ For more information, read [the Lovable Cloud FAQ](https://docs.lovable.dev/feat
|
||||
|
||||
</AccordionItem>
|
||||
|
||||
</div>
|
||||
|
||||
</Accordion>
|
||||
@@ -13,7 +13,7 @@ database_id = "04801b69-e7eb-4f40-8d41-81110397bbc2"
|
||||
|
||||
Each ORM or library configures prepared statements differently. Here are settings for some common ones. If you don't see yours, make a comment
|
||||
|
||||
# Prisma:
|
||||
## Prisma:
|
||||
|
||||
add ?pgbouncer=true to end of connection string:
|
||||
|
||||
@@ -21,7 +21,7 @@ add ?pgbouncer=true to end of connection string:
|
||||
postgres://[db-user].[project-ref]:[db-password]@aws-0-[aws-region].pooler.supabase.com:6543/[db-name]?pgbouncer=true
|
||||
```
|
||||
|
||||
# Drizzle:
|
||||
## Drizzle:
|
||||
|
||||
Add a prepared false flag to the client:
|
||||
|
||||
@@ -29,7 +29,7 @@ Add a prepared false flag to the client:
|
||||
export const client = postgres(connectionString, { prepare: false })
|
||||
```
|
||||
|
||||
# Node Postgres
|
||||
## Node Postgres
|
||||
|
||||
[Just omit the "name" value in a query definition](https://node-postgres.com/features/queries#prepared-statements):
|
||||
|
||||
@@ -41,16 +41,16 @@ const query = {
|
||||
}
|
||||
```
|
||||
|
||||
# Psycopg
|
||||
## Psycopg
|
||||
|
||||
set the [prepare_threshold](https://www.psycopg.org/psycopg3/docs/api/connections.html#psycopg.Connection.prepare_threshold) to `None`.
|
||||
|
||||
# asyncpg
|
||||
## asyncpg
|
||||
|
||||
Follow the recommendation in the [asyncpg docs](https://magicstack.github.io/asyncpg/current/faq.html#why-am-i-getting-prepared-statement-errors)
|
||||
|
||||
> disable automatic use of prepared statements by passing `statement_cache_size=0` to [asyncpg.connect()](https://magicstack.github.io/asyncpg/current/api/index.html#asyncpg.connection.connect) and [asyncpg.create_pool()](https://magicstack.github.io/asyncpg/current/api/index.html#asyncpg.pool.create_pool) (and, obviously, avoid the use of [Connection.prepare()](https://magicstack.github.io/asyncpg/current/api/index.html#asyncpg.connection.Connection.prepare));
|
||||
|
||||
# Rust's Deadpool or `tokio-postgres`:
|
||||
## Rust's Deadpool or `tokio-postgres`:
|
||||
|
||||
- Check [GitHub Discussion](https://github.com/bikeshedder/deadpool/issues/340#event-13642472475)
|
||||
+12
-12
@@ -9,7 +9,7 @@ database_id = "188986c9-019d-4f26-baaf-6f58cec8fa7a"
|
||||
|
||||
> A complimentary [guide](https://github.com/orgs/supabase/discussions/26224) was made for the Postgres logs
|
||||
|
||||
# Navigating the API logs:
|
||||
## Navigating the API logs:
|
||||
|
||||
The Database API is powered by a [ PostgREST web-server](https://postgrest.org/en/v12/), recording every request to the API Edge Network logs. To precisely navigate them, use the [Log Explorer](/dashboard/project/_/logs/explorer). These logs are managed through [Logflare](/blog/supabase-logs-self-hosted) and can be queried with a subset of BigQuery SQL syntax.
|
||||
|
||||
@@ -45,9 +45,9 @@ The most useful fields for debugging are:
|
||||
|
||||
> NOTE: not every field is included below. For a full list, check the API Edge field reference in the [Log Explorer](/dashboard/project/_/logs/explorer)
|
||||
|
||||
## Request object
|
||||
### Request object
|
||||
|
||||
### Cloudflare geographic data:
|
||||
#### Cloudflare geographic data:
|
||||
|
||||
**Suggested use cases:**
|
||||
|
||||
@@ -79,7 +79,7 @@ cross join unnest(request) AS request;
|
||||
cross join unnest(cf) AS cf;
|
||||
```
|
||||
|
||||
### IP and browser/environment data:
|
||||
#### IP and browser/environment data:
|
||||
|
||||
**Suggested use cases:**
|
||||
|
||||
@@ -107,7 +107,7 @@ cross join unnest(request) AS request;
|
||||
cross join unnest(headers) AS headers;
|
||||
```
|
||||
|
||||
### Query type and formatting data:
|
||||
#### Query type and formatting data:
|
||||
|
||||
**Suggested use cases:**
|
||||
|
||||
@@ -137,9 +137,9 @@ cross join unnest(request) AS request;
|
||||
cross join unnest(sb) AS sb;
|
||||
```
|
||||
|
||||
## Response object
|
||||
### Response object
|
||||
|
||||
### Status code:
|
||||
#### Status code:
|
||||
|
||||
**Suggested use cases:**
|
||||
|
||||
@@ -162,9 +162,9 @@ from
|
||||
cross join unnest(response) as response;
|
||||
```
|
||||
|
||||
# Finding errors
|
||||
## Finding errors
|
||||
|
||||
### API level errors
|
||||
#### API level errors
|
||||
|
||||
The `metadata.request.url` contains PostgREST formatted queries.
|
||||
|
||||
@@ -213,7 +213,7 @@ where
|
||||
|
||||
PostgREST has an [error reference table](https://postgrest.org/en/v12/references/errors.html) that you can use to interpret status codes.
|
||||
|
||||
### Database-level errors
|
||||
#### Database-level errors
|
||||
|
||||
However, some errors that are reported through the Database API occur at the Postgres level. If it is not clear which error occurred you should reference the timestamp of the error and try to see if you can find it in the Postgres logs.
|
||||
|
||||
@@ -248,11 +248,11 @@ limit 100;
|
||||
|
||||
Like PostgREST, Postgres has a [reference table](https://www.postgresql.org/docs/current/errcodes-appendix.html) for interpreting error codes.
|
||||
|
||||
## PostgREST server and Cloudflare errors
|
||||
### PostgREST server and Cloudflare errors
|
||||
|
||||
In some cases, errors may emerge because of Cloudflare or PostgREST server errors. For 500 and above errors, you may want to check your [PostgREST](/dashboard/project/_/logs/postgrest-logs) logs and the [Cloudflare docs.](https://developers.cloudflare.com/support/troubleshooting/cloudflare-errors/troubleshooting-cloudflare-5xx-errors/#error-502-bad-gateway-or-error-504-gateway-timeout))
|
||||
|
||||
# Practical examples:
|
||||
## Practical examples:
|
||||
|
||||
**Find All Errors:**
|
||||
|
||||
|
||||
@@ -143,8 +143,7 @@ Your project uses the [new asymmetric keys](/blog/jwt-signing-keys) for authenti
|
||||
size="medium"
|
||||
className="text-foreground-light mt-8 mb-6"
|
||||
>
|
||||
<div className="border-b mt-3 pb-3">
|
||||
<AccordionItem
|
||||
<AccordionItem
|
||||
header="Method A: Dashboard"
|
||||
id="item-1"
|
||||
>
|
||||
@@ -154,10 +153,7 @@ In the [Functions Dashboard](/dashboard/project/_/functions/), open the affected
|
||||

|
||||
|
||||
</AccordionItem>
|
||||
|
||||
</div>
|
||||
<div className="border-b mt-3 pb-3">
|
||||
<AccordionItem
|
||||
<AccordionItem
|
||||
header="Method B: Supabase CLI"
|
||||
id="item-2"
|
||||
>
|
||||
@@ -170,9 +166,7 @@ supabase functions deploy YOUR_FUNCTION_NAME --no-verify-jwt
|
||||
|
||||
</AccordionItem>
|
||||
|
||||
</div>
|
||||
<div className="border-b mt-3 pb-3">
|
||||
<AccordionItem
|
||||
<AccordionItem
|
||||
header="Method C: Management API"
|
||||
id="item-3"
|
||||
>
|
||||
@@ -192,7 +186,6 @@ curl 'https://api.supabase.com/v1/projects/PROJECT_ID/functions/FUNCTION_NAME' \
|
||||
|
||||
</AccordionItem>
|
||||
|
||||
</div>
|
||||
</Accordion>
|
||||
|
||||
### Invalid key
|
||||
|
||||
@@ -141,7 +141,6 @@ There are a few other queries that may be useful for identifying patterns around
|
||||
size="medium"
|
||||
className="text-foreground-light mt-8 mb-6"
|
||||
>
|
||||
<div className="border-b mt-3 pb-3">
|
||||
<AccordionItem
|
||||
header="Checking if a specific version is an offender"
|
||||
id="item-1"
|
||||
@@ -169,8 +168,6 @@ order by pct_546;
|
||||
|
||||
</AccordionItem>
|
||||
|
||||
</div>
|
||||
<div className="border-b mt-3 pb-3">
|
||||
<AccordionItem
|
||||
header="Check error frequency by time"
|
||||
id="item-2"
|
||||
@@ -208,9 +205,6 @@ LIMIT 24;
|
||||
|
||||
</AccordionItem>
|
||||
|
||||
</div>
|
||||
|
||||
<div className="border-b mt-3 pb-3">
|
||||
<AccordionItem
|
||||
header="Check requests per isolate"
|
||||
id="item-3"
|
||||
@@ -246,7 +240,6 @@ GROUP BY metadata.execution_id
|
||||
|
||||
</AccordionItem>
|
||||
|
||||
</div>
|
||||
</Accordion>
|
||||
|
||||
## Step 3: Correcting the error
|
||||
|
||||
@@ -12,7 +12,7 @@ cli = [ "supabase-postgres-config-update" ]
|
||||
|
||||
> WARNING: Manually configuring the connection count hard codes it. This means if you upgrade or downgrade your database, the connection count will not auto-resize. You will have to make sure to manually update it.
|
||||
|
||||
# Changing max database connections:
|
||||
## Changing max database connections
|
||||
|
||||
Each compute instance has a default direct connection and pooler connection settings. You can find the most recent settings in the [compute docs](/docs/guides/platform/compute-add-ons#disk-io):
|
||||
|
||||
@@ -30,7 +30,7 @@ Each compute instance has a default direct connection and pooler connection sett
|
||||
| 12XL | 500 | 9,000 |
|
||||
| 16XL | 500 | 12,000 |
|
||||
|
||||
## Configuring direct connections limits
|
||||
### Configuring direct connections limits
|
||||
|
||||
> Note: the Supavisor connection limits are hard-coded and cannot be changed without upgrading the compute size:
|
||||
|
||||
@@ -50,21 +50,21 @@ Then you could run the following SQL in the SQL Editor to see if the changes wen
|
||||
SHOW max_connections;
|
||||
```
|
||||
|
||||
# Dangers of increasing the direct connection limits
|
||||
## Dangers of increasing the direct connection limits
|
||||
|
||||
**Three** factors must be taken into consideration when adjusting the direct connection limit:
|
||||
|
||||
### Process schedulers and Postgres internals:
|
||||
#### Process schedulers and Postgres internals
|
||||
|
||||
Allowing too many direct connections in your database can overburden Postgres schedulers and other internal modules. This will result in a noticeable decrease in query throughput, despite having more connections available. EnterpriseDB wrote a wonderful [article](https://www.enterprisedb.com/postgres-tutorials/why-you-should-use-connection-pooling-when-setting-maxconnections-postgres) that outlines some of the considerations.
|
||||
|
||||
The default connection values are set based on a solid understanding of Postgres architecture, and straying too far from them is _likely_ to hinder performance. However, with some experimentation, you might discover a value better suited to your specific needs. Still, unless there's a compelling reason to adjust the setting, it's generally advisable to stick with the defaults or change the values judiciously.'
|
||||
|
||||
### Memory
|
||||
#### Memory
|
||||
|
||||
> If you do not know how to monitor memory and CPU with Supabase Grafana, [check here](https://github.com/orgs/supabase/discussions/27141).
|
||||
|
||||
#### Each direct connection is a running process that will consume active memory
|
||||
##### Each direct connection is a running process that will consume active memory
|
||||
|
||||
This is a Grafana Chart of unhealthy memory usage:
|
||||
|
||||
@@ -92,7 +92,7 @@ select
|
||||
) || ' * ' || current_setting('maintenance_work_mem') || ')) / ' || current_setting('work_mem');
|
||||
```
|
||||
|
||||
### CPU
|
||||
#### CPU
|
||||
|
||||
The below chart is an example of what can occur to the CPU if 100s of connections are inappropriately opened/closed every second or many CPU intensive queries are run in parallel
|
||||
|
||||
|
||||
+30
-30
@@ -8,11 +8,11 @@ database_id = "8b000bb4-180b-4a6c-b280-ba02965060f6"
|
||||
|
||||
> A complimentary guide was made for the [API logs](https://github.com/orgs/supabase/discussions/22849)
|
||||
|
||||
# Debugging and monitoring Postgres with logs
|
||||
## Debugging and monitoring Postgres with logs
|
||||
|
||||
Logs provide insights into Postgres operations. They help meet compliance requirements, detect suspicious activity, and troubleshoot problems.
|
||||
|
||||
## Table of contents
|
||||
### Table of contents
|
||||
|
||||
- Querying Logs
|
||||
- `postgres_logs` Table Structure
|
||||
@@ -33,7 +33,7 @@ Logs provide insights into Postgres operations. They help meet compliance requir
|
||||
- Frequently Asked Questions
|
||||
- Other resources
|
||||
|
||||
## Querying logs
|
||||
### Querying logs
|
||||
|
||||
The most practical way to explore and filter logs is through the [Logs Explorer](/dashboard/project/_/logs/explorer).
|
||||
|
||||
@@ -47,7 +47,7 @@ Although there are many strategies to filter logs, such as `like` and `in` state
|
||||
|
||||
The `postgres_logs` table contains Postgres events.
|
||||
|
||||
### `postgres_logs` table structure
|
||||
#### `postgres_logs` table structure
|
||||
|
||||
The table contains 3 fundamental columns:
|
||||
|
||||
@@ -73,9 +73,9 @@ cross join unnest(metadata) AS metadata
|
||||
cross join unnest(parsed) AS parsed;
|
||||
```
|
||||
|
||||
### Parsed metadata fields
|
||||
#### Parsed metadata fields
|
||||
|
||||
#### Query information
|
||||
##### Query information
|
||||
|
||||
| Field | Description | Example |
|
||||
| --------------------- | ------------------------------------------------------------------------------------------------------ | ------------------------------- |
|
||||
@@ -88,7 +88,7 @@ cross join unnest(parsed) AS parsed;
|
||||
- Identifying slow queries
|
||||
- Identifying failing queries
|
||||
|
||||
#### Error/Warning information
|
||||
##### Error/Warning information
|
||||
|
||||
| Field | Description | Example |
|
||||
| --------------------- | ------------------------------------------------------ | --------------------------------------------------------------------------------------------------- |
|
||||
@@ -103,7 +103,7 @@ cross join unnest(parsed) AS parsed;
|
||||
- Filter by error severity or SQL code
|
||||
- Get hints, details, and context about error events
|
||||
|
||||
#### Connection/Identification information
|
||||
##### Connection/Identification information
|
||||
|
||||
| Field | Description | Example |
|
||||
| ------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------- |
|
||||
@@ -124,9 +124,9 @@ cross join unnest(parsed) AS parsed;
|
||||
- Filter connections by sessions for debugging
|
||||
- identify extension events
|
||||
|
||||
## Filtering logs
|
||||
### Filtering logs
|
||||
|
||||
### Excluding routine events
|
||||
#### Excluding routine events
|
||||
|
||||
Most Postgres logs during normal periods are routine events, such as connection authorizations and checkpoints. To see the default types of events that are logged, you can check this [guide](https://gist.github.com/TheOtherBrian1/991d32c2b00dbc75d29b80d4cdf41aa7).
|
||||
|
||||
@@ -139,7 +139,7 @@ where
|
||||
not regexp_contains(event_message, '^cron|PgBouncer|checkpoint|connection received|authenticated|authorized');
|
||||
```
|
||||
|
||||
### By timeframe
|
||||
#### By timeframe
|
||||
|
||||
To investigate issues around a specific period:
|
||||
|
||||
@@ -150,7 +150,7 @@ where
|
||||
timestamp between '2024-05-06 04:44:00' and '2024-05-06 04:45:00'
|
||||
```
|
||||
|
||||
### By error severity
|
||||
#### By error severity
|
||||
|
||||
This filter finds all errors, fatals, and panics:
|
||||
|
||||
@@ -169,7 +169,7 @@ where
|
||||
|
||||
Failure events include an sql_state_code that can be referenced in the [Postgres Docs](https://www.postgresql.org/docs/current/errcodes-appendix.html)
|
||||
|
||||
### By query
|
||||
#### By query
|
||||
|
||||
> NOTE: Unless pg_audit is configured, only failed queries are logged
|
||||
|
||||
@@ -187,11 +187,11 @@ Queries can use complex syntax, so it is often helpful to isolate by referenced
|
||||
- `^`: look for values at start of string
|
||||
- `|`: or operator
|
||||
|
||||
## By APIs/roles
|
||||
### By APIs/roles
|
||||
|
||||
All failed queries, including those from PostgREST, Auth, and external libraries (e.g., Prisma) are logged with helpful error messages for debugging.
|
||||
|
||||
#### Server/Role mapping
|
||||
##### Server/Role mapping
|
||||
|
||||
API servers have assigned database roles for connecting to the database:
|
||||
|
||||
@@ -217,7 +217,7 @@ where
|
||||
...
|
||||
```
|
||||
|
||||
## By Dashboard queries
|
||||
### By Dashboard queries
|
||||
|
||||
Queries from the Supabase Dashboard are executed under the `postgres` role and include the comment `-- source: dashboard`. To isolate or exclude Dashboard requests during debugging, you can filter by this comment.
|
||||
|
||||
@@ -228,7 +228,7 @@ where
|
||||
regexp_contains(parsed.query, '-- source: dashboard')
|
||||
```
|
||||
|
||||
## Full example for finding errors
|
||||
### Full example for finding errors
|
||||
|
||||
```sql
|
||||
select
|
||||
@@ -255,9 +255,9 @@ order by timestamp desc
|
||||
limit 100;
|
||||
```
|
||||
|
||||
# Logging for compliance and security
|
||||
## Logging for compliance and security
|
||||
|
||||
### Customized object and role activity logging
|
||||
#### Customized object and role activity logging
|
||||
|
||||
> ⚠️ NOTE: This is specifically designated for those using the `postgres` role or [custom roles](/docs/guides/database/postgres/roles) to interact with their database. Those using the Database REST API should reference the [Database API Logging Guide](https://github.com/orgs/supabase/discussions/22849) instead.
|
||||
|
||||
@@ -279,7 +279,7 @@ where
|
||||
parsed.user_name = 'API_role'
|
||||
```
|
||||
|
||||
### Filtering by IP
|
||||
#### Filtering by IP
|
||||
|
||||
> If you are connecting from a known, limited range of IP addresses, you should enable [network restrictions](/docs/guides/platform/network-restrictions).
|
||||
|
||||
@@ -306,7 +306,7 @@ order by ip_count desc
|
||||
limit 100;
|
||||
```
|
||||
|
||||
# Reviewing log settings
|
||||
## Reviewing log settings
|
||||
|
||||
The `pg_settings` table describes system and logging configurations.
|
||||
|
||||
@@ -337,11 +337,11 @@ where
|
||||
and name like '%log%';
|
||||
```
|
||||
|
||||
## Changing log settings
|
||||
### Changing log settings
|
||||
|
||||
> WARNING: lenient settings can lead to over-logging, impacting database performance while creating noise in the logs.
|
||||
|
||||
#### Severity levels
|
||||
##### Severity levels
|
||||
|
||||
The `log_min_messages` variable determines what is severe enough to log. Here are the severity thresholds from the [Postgres docs](https://www.postgresql.org/docs/current/runtime-config-logging.html).
|
||||
|
||||
@@ -365,7 +365,7 @@ alter role postgres set log_min_messages = '<NEW VALUE>';
|
||||
show log_min_messages; -- default WARNING
|
||||
```
|
||||
|
||||
#### Configuring queries logged
|
||||
##### Configuring queries logged
|
||||
|
||||
By default, only failed queries are logged. The [PGAudit extension](/docs/guides/database/extensions/pgaudit) extends Postgres's built-in logging abilities. It can be used to selectively track all queries in your database by:
|
||||
|
||||
@@ -374,25 +374,25 @@ By default, only failed queries are logged. The [PGAudit extension](/docs/guides
|
||||
- database object
|
||||
- entire database
|
||||
|
||||
#### Logging within database functions
|
||||
##### Logging within database functions
|
||||
|
||||
To track or debug functions, logging can be configured by following the [function debugging guide](/docs/guides/database/functions#general-logging)
|
||||
|
||||
# Frequently Asked Questions
|
||||
## Frequently Asked Questions
|
||||
|
||||
#### How to join different log tables
|
||||
##### How to join different log tables
|
||||
|
||||
No, log tables are independent from each other and do not share any primary/foreign key relations for joining.
|
||||
|
||||
#### How to download logs
|
||||
##### How to download logs
|
||||
|
||||
At the moment, the way to download logs is through the Log Dashboard as a CSV
|
||||
|
||||
#### What is logged?
|
||||
##### What is logged?
|
||||
|
||||
To see the default types of events that are logged, you can check this [guide](https://gist.github.com/TheOtherBrian1/991d32c2b00dbc75d29b80d4cdf41aa7).
|
||||
|
||||
### Other resources:
|
||||
#### Other resources:
|
||||
|
||||
- [Regex for filtering logs](https://github.com/orgs/supabase/discussions/22640)
|
||||
- [Debugging with the DB API logs](https://github.com/orgs/supabase/discussions/22849)
|
||||
|
||||
@@ -60,8 +60,7 @@ If the page displays the Supabase icon, your Supabase project name, and some lin
|
||||
size="medium"
|
||||
className="text-foreground-light mt-8 mb-6"
|
||||
>
|
||||
<div className="border-b mt-3 pb-3">
|
||||
<AccordionItem
|
||||
<AccordionItem
|
||||
header="Does this affect where my app is hosted?"
|
||||
id="app-hosting"
|
||||
>
|
||||
@@ -70,10 +69,7 @@ If the page displays the Supabase icon, your Supabase project name, and some lin
|
||||
|
||||
</AccordionItem>
|
||||
|
||||
</div>
|
||||
|
||||
<div className="border-b mt-3 pb-3">
|
||||
<AccordionItem
|
||||
<AccordionItem
|
||||
header="Can I access my database URL or API keys if I'm on Lovable Cloud?"
|
||||
id="database-api-access"
|
||||
>
|
||||
@@ -82,10 +78,7 @@ If the page displays the Supabase icon, your Supabase project name, and some lin
|
||||
|
||||
</AccordionItem>
|
||||
|
||||
</div>
|
||||
|
||||
<div className="border-b mt-3 pb-3">
|
||||
<AccordionItem
|
||||
<AccordionItem
|
||||
header="Can I switch from Lovable Cloud to my own Supabase backend?"
|
||||
id="switch-backend"
|
||||
>
|
||||
@@ -96,7 +89,6 @@ If the page displays the Supabase icon, your Supabase project name, and some lin
|
||||
|
||||
</AccordionItem>
|
||||
|
||||
</div>
|
||||
</Accordion>
|
||||
|
||||
## Lovable Cloud – specific questions
|
||||
@@ -109,8 +101,7 @@ If the page displays the Supabase icon, your Supabase project name, and some lin
|
||||
size="medium"
|
||||
className="text-foreground-light mt-8 mb-6"
|
||||
>
|
||||
<div className="border-b mt-3 pb-3">
|
||||
<AccordionItem
|
||||
<AccordionItem
|
||||
header="My project uses Lovable Cloud, can I move it to Supabase?"
|
||||
id="move-to-supabase"
|
||||
>
|
||||
@@ -123,10 +114,7 @@ If the page displays the Supabase icon, your Supabase project name, and some lin
|
||||
|
||||
</AccordionItem>
|
||||
|
||||
</div>
|
||||
|
||||
<div className="border-b mt-3 pb-3">
|
||||
<AccordionItem
|
||||
<AccordionItem
|
||||
header="My project uses Lovable Cloud, but I can't see my Supabase project."
|
||||
id="cant-see-project"
|
||||
>
|
||||
@@ -137,5 +125,4 @@ If the page displays the Supabase icon, your Supabase project name, and some lin
|
||||
|
||||
</AccordionItem>
|
||||
|
||||
</div>
|
||||
</Accordion>
|
||||
+3
-3
@@ -9,7 +9,7 @@ database_id = "ef05da0a-f8bc-44a4-9719-5ae811dba104"
|
||||
|
||||
> [Guide](/docs/guides/monitoring-troubleshooting/metrics#deploying-supabase-grafana) for setting up Supabase Grafana
|
||||
|
||||
# CPU
|
||||
## CPU
|
||||
|
||||
Here are examples of unhealthy CPU utilization:
|
||||
|
||||
@@ -25,13 +25,13 @@ The CPU chart shows 4 distinct metrics of interest:
|
||||
|
||||
As the CPU peaks towards 100%, queries and database tasks will begin to throttle, as they won't have enough time or access to the CPU.
|
||||
|
||||
### Other useful Supabase Grafana guides:
|
||||
#### Other useful Supabase Grafana guides:
|
||||
|
||||
- [Connections](https://github.com/orgs/supabase/discussions/27141)
|
||||
- [Disk](https://github.com/orgs/supabase/discussions/27003)
|
||||
- [Memory](https://github.com/orgs/supabase/discussions/27021)
|
||||
|
||||
### Optimizing:
|
||||
#### Optimizing
|
||||
|
||||
1. [Optimize your queries](/docs/guides/database/query-optimization).
|
||||
2. [Add indexes](https://github.com/orgs/supabase/discussions/22449) if possible.
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
---
|
||||
title = "Kong stops responding under heavy load in local development"
|
||||
topics = [ "cli", "self-hosting", "storage" ]
|
||||
keywords = [ "kong", "local", "workers", "nginx", "worker_processes", "storage", "concurrent", "timeout", "socket" ]
|
||||
|
||||
[api]
|
||||
cli = [ "supabase-start" ]
|
||||
---
|
||||
|
||||
When running Supabase locally with the CLI, the Kong API gateway can stop
|
||||
responding under heavy load. This typically happens when many parallel
|
||||
requests are made (for example, bulk operations against the Storage API):
|
||||
Kong starts terminating socket connections and logs errors about not having
|
||||
enough available workers.
|
||||
|
||||
## Why this happens
|
||||
|
||||
To keep the local stack lightweight, the CLI starts Kong with a single nginx
|
||||
worker process (`KONG_NGINX_WORKER_PROCESSES=1`). A single worker minimizes
|
||||
memory usage across the ~12 containers that make up the local stack, but it
|
||||
also limits how many concurrent connections Kong can handle. When the number
|
||||
of in-flight requests exceeds what one worker can serve, Kong becomes
|
||||
unresponsive and drops connections.
|
||||
|
||||
## How to fix it
|
||||
|
||||
You can override the number of Kong nginx worker processes by setting the
|
||||
`KONG_NGINX_WORKER_PROCESSES` environment variable before starting the local
|
||||
stack. Set it to a specific number, or to `auto` to let Kong allocate one
|
||||
worker per available CPU:
|
||||
|
||||
```bash
|
||||
# Use one worker per CPU core
|
||||
KONG_NGINX_WORKER_PROCESSES=auto supabase start
|
||||
|
||||
# Or pick a fixed number of workers
|
||||
KONG_NGINX_WORKER_PROCESSES=2 supabase start
|
||||
```
|
||||
|
||||
You can also export the variable so it applies to every command in your shell
|
||||
session:
|
||||
|
||||
```bash
|
||||
export KONG_NGINX_WORKER_PROCESSES=auto
|
||||
supabase start
|
||||
```
|
||||
|
||||
Increasing the worker count lets Kong handle more parallel connections at the
|
||||
cost of higher memory usage. If you don't set the variable, the CLI keeps the
|
||||
default of `1` worker to minimize the local stack's memory footprint.
|
||||
|
||||
After changing the value, restart the stack for it to take effect:
|
||||
|
||||
```bash
|
||||
supabase stop
|
||||
KONG_NGINX_WORKER_PROCESSES=auto supabase start
|
||||
```
|
||||
|
||||
## Additional resources
|
||||
|
||||
- [Local development guide](/docs/guides/cli/local-development)
|
||||
- [CLI repository](https://github.com/supabase/cli)
|
||||
@@ -9,7 +9,7 @@ database_id = "9a55c946-877f-46ae-8b57-51934e02a36c"
|
||||
|
||||
This is a general guide for debugging pg_cron. Below lists issues and how to debug them
|
||||
|
||||
# Cannot create/edit/delete cron jobs
|
||||
## Cannot create/edit/delete cron jobs
|
||||
|
||||
Cron jobs can only be modified with the respective SQL functions:
|
||||
|
||||
@@ -23,13 +23,13 @@ If you are trying to make changes, use the cron functions. If the cron functions
|
||||
|
||||
---
|
||||
|
||||
# Cron Jobs are not running
|
||||
## Cron Jobs are not running
|
||||
|
||||
> You should consider initiating a software upgrade in the [Infrastructure Settings](/dashboard/project/_/settings/infrastructure) if your Postgres version is below v15.6.1.122. Upgrading will give you access to pg_cron v1.6.4+, which has many bug fixes and auto-revive capabilities.
|
||||
|
||||
## Debugging steps:
|
||||
### Debugging steps:
|
||||
|
||||
### Check to see if "pg_cron scheduler" is active
|
||||
#### Check to see if `pg_cron scheduler` is active
|
||||
|
||||
pg_cron operates as the `pg_cron scheduler` process within Postgres. Use the below query to check if the worker is active
|
||||
|
||||
@@ -56,7 +56,7 @@ If the query does not return a row, the worker has died. To revive it, you must
|
||||
|
||||
<br />
|
||||
|
||||
### Check the `cron.job_run_details` table for more information
|
||||
#### Check the `cron.job_run_details` table for more information
|
||||
|
||||
pg_cron creates logs in its own table `cron.job_run_details`. The below query checks for issues from the past 5 days :
|
||||
|
||||
@@ -77,7 +77,7 @@ Respond to the errors exposed appropriately.
|
||||
|
||||
<br />
|
||||
|
||||
### Check if there are too many cron jobs running concurrently
|
||||
#### Check if there are too many cron jobs running concurrently
|
||||
|
||||
pg_cron supports up to 32 concurrent jobs, each using a database connection. If too many jobs are running simultaneously, space them out to prevent connection overload and job failure.
|
||||
|
||||
@@ -110,7 +110,7 @@ You can view your concurrent peak connection usage throughout the day at the bot
|
||||
|
||||
<br />
|
||||
|
||||
### Check for database strain
|
||||
#### Check for database strain
|
||||
|
||||
Unfortunately, excessive resource strain can slow down or disrupt jobs.
|
||||
|
||||
@@ -125,7 +125,7 @@ It is important to make sure you are running the latest release of pg_cron (1.6.
|
||||
|
||||
<br />
|
||||
|
||||
### Check the log explorer for more information
|
||||
#### Check the log explorer for more information
|
||||
|
||||
Although pg*cron records errors in the `cron.job_run_details` table, in rare cases, more information can be found in the general Postgres logs. You can check the [Log Explorer](/dashboard/project/*/logs/explorer) for failure events with the following query
|
||||
|
||||
@@ -156,7 +156,7 @@ If you're interested in modifying the query, there is an advanced [guide](https:
|
||||
|
||||
<br />
|
||||
|
||||
### Create custom logs within cron jobs
|
||||
#### Create custom logs within cron jobs
|
||||
|
||||
If it's still not clear what is occurring you may be able to capture more logs by running the pg_cron query inside a database function:
|
||||
|
||||
@@ -188,12 +188,12 @@ You can then search for your custom messages in the [Logs Interface](/dashboard/
|
||||
|
||||
<br />
|
||||
|
||||
### Upgrading pg_cron version
|
||||
#### Upgrading pg_cron version
|
||||
|
||||
The current version of pg*cron on Supabase is 1.6.4. It comes with a [few bug fixes](https://github.com/citusdata/pg_cron/releases/tag/v1.6.4). You should consider upgrading to Postgres v15.6.1.122+ in the[ Infrastructure Settings](/dashboard/project/*/settings/infrastructure) to get the latest extension.
|
||||
|
||||
<br />
|
||||
|
||||
### Contacting support and the maintainers
|
||||
#### Contacting support and the maintainers
|
||||
|
||||
Although Supabase includes the extension, it is maintained by Citus (a Microsoft subsidiary). You can contact Support for more help, but you should also consider creating an issue in the [pg_cron repo](https://github.com/citusdata/pg_cron).
|
||||
-1
@@ -6,7 +6,6 @@ keywords = [ "postgrest", "column does not exist" ]
|
||||
[[errors]]
|
||||
http_status_code = 400
|
||||
message = "column example_table.example_column does not exist"
|
||||
|
||||
---
|
||||
|
||||
If you receive a `400` error with the message `column example_table.example_column does not exist` only on mutation requests (`PATCH`, `POST`, `DELETE`) — while `SELECT` queries on the same column work fine — this is a known bug in PostgREST versions before 14.4 ([issue #3707](https://github.com/PostgREST/postgrest/issues/3707)).
|
||||
|
||||
@@ -27,7 +27,7 @@ message = "Drift detected: Your database schema is not in sync with your migrati
|
||||
|
||||
> This guide has been deprecated. Use the troubleshooting guide in the [Supabase docs](/docs/guides/database/prisma/prisma-troubleshooting).
|
||||
|
||||
# Addressing specific errors:
|
||||
## Addressing specific errors
|
||||
|
||||
Prisma, unlike other libraries, uses [query parameters for configurations](https://www.prisma.io/docs/orm/overview/databases/postgresql#arguments).
|
||||
|
||||
@@ -37,7 +37,7 @@ Some can be used to address specific errors and can be appended to end of your c
|
||||
.../postgres?KEY1=VALUE&KEY2=VALUE&KEY3=VALUE
|
||||
```
|
||||
|
||||
## `Can't reach database server at`:
|
||||
### Can't reach database server
|
||||
|
||||
Increase `connect_timeout` to 30s and check to make sure you are using a valid connection string.
|
||||
|
||||
@@ -45,7 +45,7 @@ Increase `connect_timeout` to 30s and check to make sure you are using a valid c
|
||||
.../postgres?connect_timeout=30
|
||||
```
|
||||
|
||||
## `Timed out fetching a new connection from the connection pool`:
|
||||
### Timed out fetching a new connection from the connection pool
|
||||
|
||||
Increase `pool_timeout` to 30s .
|
||||
|
||||
@@ -53,7 +53,7 @@ Increase `pool_timeout` to 30s .
|
||||
.../postgres?pool_timeout=30
|
||||
```
|
||||
|
||||
## `... prepared statement "" already exists`
|
||||
### Prepared statement already exists
|
||||
|
||||
Add pgbouncer=true to the connection string.
|
||||
|
||||
@@ -61,23 +61,23 @@ Add pgbouncer=true to the connection string.
|
||||
.../postgres?pgbouncer=true
|
||||
```
|
||||
|
||||
## `Max client connections reached`
|
||||
### Max client connections reached
|
||||
|
||||
Check out this [guide](https://github.com/orgs/supabase/discussions/22305) for managing this error
|
||||
|
||||
## `Server has closed the connection`
|
||||
### Server has closed the connection
|
||||
|
||||
According to this [GitHub Issue for Prisma](https://github.com/prisma/prisma/discussions/7389), it may be related to large return values for queries. Try to limit the total amount of rows returned for particularly large requests.
|
||||
|
||||
## `Drift detected: Your database schema is not in sync with your migration history`
|
||||
### Drift detected: Your database schema is not in sync with your migration history
|
||||
|
||||
Prisma will try to act as the source of truth for your database structures. If you `CREATE`, `DROP`, or `ALTER` database objects outside of a Prisma Migration, it is likely to detect drift and may offer to correct the situation by purging your schemas. To circumvent this issue, try [baselining your migrations](https://www.prisma.io/docs/orm/prisma-migrate/workflows/baselining).
|
||||
|
||||
Some users have discussed how they managed this problem in a [GitHub Discussion.](https://github.com/prisma/prisma/issues/19100#top)
|
||||
|
||||
# Management suggestions
|
||||
## Management suggestions
|
||||
|
||||
## Make a custom role for Prisma to increase observability
|
||||
### Make a custom role for Prisma to increase observability
|
||||
|
||||
**Imagine your database as a house, and users as the people with keys.**
|
||||
|
||||
@@ -85,7 +85,7 @@ Some users have discussed how they managed this problem in a [GitHub Discussion.
|
||||
- it's usually safer to give Prisma its own key! This way, it can only access the rooms (tables) it needs.
|
||||
- Plus, with separate keys, it's easier to see what Prisma is doing in your house with monitoring tools, such as [PGAudit](/docs/guides/database/extensions/pgaudit?queryGroups=database-method&database-method=sql) and [pg_stat_activity](/docs/guides/platform/performance).
|
||||
|
||||
### Creating the Prisma user:
|
||||
#### Creating the Prisma user
|
||||
|
||||
```sql
|
||||
create user "prisma" with password 'secret_password' bypassrls createdb;
|
||||
@@ -93,7 +93,7 @@ create user "prisma" with password 'secret_password' bypassrls createdb;
|
||||
|
||||
> Prisma requires the [`createdb` modifier](/blog/postgres-roles-and-privileges#role-attributes) to create shadow databases. It uses them to help manage migrations.
|
||||
|
||||
### Give Postgres ownership of the new user:
|
||||
#### Give Postgres ownership of the new user
|
||||
|
||||
This allows you to view Prisma migration changes in the [Dashboard](/dashboard/project/_/editor)
|
||||
|
||||
@@ -101,7 +101,7 @@ This allows you to view Prisma migration changes in the [Dashboard](/dashboard/p
|
||||
grant "prisma" to "postgres";
|
||||
```
|
||||
|
||||
### Keep it safe!
|
||||
#### Keep it safe!
|
||||
|
||||
Use a strong password for Prisma. Bitwarden provides a free [password generator](https://bitwarden.com/password-generator/) that can make one for you.
|
||||
|
||||
@@ -111,7 +111,7 @@ If you need to change it later, you can use the below SQL:
|
||||
alter user "prisma" with password 'new_password';
|
||||
```
|
||||
|
||||
### Grant Prisma access
|
||||
#### Grant Prisma access
|
||||
|
||||
The below example gives Prisma full authority over all database objects in the public schema:
|
||||
|
||||
@@ -129,7 +129,7 @@ The below example gives Prisma full authority over all database objects in the p
|
||||
|
||||
> For more guidance on specifying access, check out this [article](/blog/postgres-roles-and-privileges#creating-objects-and-assigning-privileges) on privileges
|
||||
|
||||
## Optimize Prisma queries:
|
||||
### Optimize Prisma queries
|
||||
|
||||
In the [Query Performance Advisor](/dashboard/project/_/database/query-performance), you can view long-running or frequently accessed queries by role:
|
||||
|
||||
@@ -141,7 +141,7 @@ In the [Query Performance Advisor](/dashboard/project/_/database/query-performan
|
||||
|
||||
Selecting a query can reveal suggestions to improve its performance
|
||||
|
||||
## Configuring connections
|
||||
### Configuring connections
|
||||
|
||||
Useful Links:
|
||||
|
||||
@@ -150,7 +150,7 @@ Useful Links:
|
||||
|
||||
Supabase provides 3 database connection strings that can be used simultaneously if necessary. You can find them on the dashboard by clicking [Connect](/dashboard/project/_?showConnect=true).
|
||||
|
||||
### Direct connection:
|
||||
#### Direct connection
|
||||
|
||||
Best used with stationary servers, such as VMs and long-standing containers, but it only works in IPv6 environments unless the [IPv4 Add-On](/dashboard/project/_/settings/addons) is enabled. If you are unsure if your network is IPv6 compatible, [check here](https://github.com/orgs/supabase/discussions/27034).
|
||||
|
||||
@@ -160,7 +160,7 @@ Best used with stationary servers, such as VMs and long-standing containers, but
|
||||
postgresql://postgres:[PASSWORD]@db.[PROJECT REF].supabase.co:5432/postgres
|
||||
```
|
||||
|
||||
### Supavisor in session mode (port 5432):
|
||||
#### Supavisor in session mode (port 5432)
|
||||
|
||||
```md
|
||||
# Example Connection
|
||||
@@ -172,7 +172,7 @@ An alternative to direct connections when working in IPv4-only environments.
|
||||
|
||||
> Session mode is a good option for migrations
|
||||
|
||||
### Supavisor in transaction mode (port 6543):
|
||||
#### Supavisor in transaction mode (port 6543)
|
||||
|
||||
```md
|
||||
# Example Connection
|
||||
|
||||
+21
-21
@@ -10,19 +10,19 @@ database_id = "031ba6d7-4928-4d95-a2da-bff8dbb740ec"
|
||||
http_status_code = 500
|
||||
---
|
||||
|
||||
# Resolving 500 status authentication errors
|
||||
## Resolving 500 status authentication errors
|
||||
|
||||
A 500 error in Auth typically indicates an issue with an external dependency, such as your database or SMTP provider, rather than with Auth itself. This guide will help you explore the Auth logs to identify the underlying cause.
|
||||
|
||||
### Prerequisites
|
||||
#### Prerequisites
|
||||
|
||||
#### Open the log explorer
|
||||
##### Open the log explorer
|
||||
|
||||
Ensure you have access to the [Dashboard's Log Explorer](/dashboard/project/_/logs/explorer) and set the time range appropriately:
|
||||
|
||||

|
||||
|
||||
#### Improving log readability
|
||||
##### Improving log readability
|
||||
|
||||
Logs are displayed in a table format, which can be challenging to read. Double-clicking on a row will expand it for easier viewing:
|
||||
|
||||
@@ -32,9 +32,9 @@ Logs are displayed in a table format, which can be challenging to read. Double-c
|
||||
src="https://github.com/user-attachments/assets/6f4c833c-ae15-41e1-9aa8-48ebd58741a1"
|
||||
/>
|
||||
|
||||
## Section 1: Checking for database-level errors
|
||||
### Section 1: Checking for database-level errors
|
||||
|
||||
### Query for recent database errors
|
||||
#### Query for recent database errors
|
||||
|
||||
Use the following SQL query to check for any recent errors the Auth server encountered while interacting with your database:
|
||||
|
||||
@@ -62,15 +62,15 @@ limit 100;
|
||||
|
||||
If no results are returned, proceed to Section 2.
|
||||
|
||||
### Common database-level errors
|
||||
#### Common database-level errors
|
||||
|
||||
There are few known categories of auth/database level errors:
|
||||
|
||||
### Constraint related (sql_state_code = 23503 or 23\*)
|
||||
#### Constraint related (sql_state_code = 23503 or 23\*)
|
||||
|
||||
If you’ve manually created a foreign key relationship between your tables and those in the `auth` schema, a constraint may prevent the Auth server from updating the `auth.users` table.
|
||||
|
||||
#### Solution
|
||||
##### Solution
|
||||
|
||||
The log will show the name of the constraint. You need `DROP` it:
|
||||
|
||||
@@ -90,11 +90,11 @@ ALTER TABLE <your table> ADD CONSTRAINT <constraint name> FOREIGN KEY (<column n
|
||||
COMMIT;
|
||||
```
|
||||
|
||||
### Ownership related (sql_state_code = 42501)
|
||||
#### Ownership related (sql_state_code = 42501)
|
||||
|
||||
If you see an error like must be owner of..., the supabase_auth_admin role may have lost privileges over tables in the auth schema. This often results from faulty migrations by external ORMs (e.g., Prisma) or manual schema modifications.
|
||||
|
||||
#### Solution
|
||||
##### Solution
|
||||
|
||||
Check ownership with this [GitHub Gist](https://gist.github.com/TheOtherBrian1/6aaaa78632b1e371f3b1c790305f0acd). If any objects are owned by the `supabase_admin` role, contact [Support](/dashboard/support/new). If they're owned by roles other than `supabase_auth_admin` you can change ownership back manually one-by-one:
|
||||
|
||||
@@ -104,7 +104,7 @@ ALTER <object type (table, function, etc.)> <auth.object_name> OWNER TO supabase
|
||||
|
||||
Alternatively, you can run the SQL script in this [GitHub Gist](https://gist.github.com/TheOtherBrian1/4714a333432b80660ff71b136b298fb8) to change all
|
||||
|
||||
### Trigger related:
|
||||
#### Trigger related:
|
||||
|
||||
If errors reference a database function, this indicates a trigger error on one of the auth tables (likely auth.users). If you do not want to keep the trigger/function, you can just quickly drop it, otherwise, continue reading to know how to fix the issue:
|
||||
|
||||
@@ -116,7 +116,7 @@ DROP FUNCTION <function name>() CASCADE;
|
||||
-- DROP TRIGGER <trigger_name> on auth.<table_name>;
|
||||
```
|
||||
|
||||
#### Solutions:
|
||||
##### Solutions:
|
||||
|
||||
Get the function's definition with this query:
|
||||
|
||||
@@ -126,11 +126,11 @@ from pg_proc
|
||||
where proname = '<FUNCTION NAME>';
|
||||
```
|
||||
|
||||
##### Trigger has insufficient privileges ( sql_state_code = 42501)
|
||||
###### Trigger has insufficient privileges ( sql_state_code = 42501)
|
||||
|
||||
If the error is related to insufficient privileges, your trigger function is missing a security definer tag, which allows it to access schemas outside of auth. You must `REPLACE` the function with the appropriate security definer settings ([example](/docs/guides/database/functions?queryGroups=language&language=js#security-definer-vs-invoker))
|
||||
|
||||
##### Trigger references a table or column that does not exist (sql_state_code = 42P01)
|
||||
###### Trigger references a table or column that does not exist (sql_state_code = 42P01)
|
||||
|
||||
The trigger may be referencing a table or column that no longer exists. In that case do one of the three:
|
||||
|
||||
@@ -139,13 +139,13 @@ The trigger may be referencing a table or column that no longer exists. In that
|
||||
- remove the trigger
|
||||
- recreate the database object that the trigger referenced
|
||||
|
||||
### Corrupted schema
|
||||
#### Corrupted schema
|
||||
|
||||
If you made any customizations to the auth schema, such as adding RLS, modifying table columns, or adding/dropping tables, it can break migrations done by the Auth Server. It's necessary to remove these changes and restore the auth schema to its original form.
|
||||
|
||||
## Section 2: Checking Auth level errors
|
||||
### Section 2: Checking Auth level errors
|
||||
|
||||
### Query for Auth errors
|
||||
#### Query for Auth errors
|
||||
|
||||
Run this SQL query in the Log Explorer to find Auth-related errors:
|
||||
|
||||
@@ -167,7 +167,7 @@ where
|
||||
order by timestamp
|
||||
```
|
||||
|
||||
### Database migration errors
|
||||
#### Database migration errors
|
||||
|
||||
> `running db migrations: Migrator: problem creating schema migrations`
|
||||
|
||||
@@ -175,7 +175,7 @@ This is a continuation of the "Corrupted Schema" error from the Postgres Section
|
||||
|
||||
If you are running older versions of auth, you may experience a migration bug. If so, checkout this [guide](https://github.com/orgs/supabase/discussions/20722) for a resolution. If it doesn't work, contact Support.
|
||||
|
||||
### SMTP errors
|
||||
#### SMTP errors
|
||||
|
||||
The logs may contain messages about `gomail`. It means that auth is struggling to communicate with the SMTP provider. This often implies that:
|
||||
|
||||
@@ -186,7 +186,7 @@ The logs may contain messages about `gomail`. It means that auth is struggling t
|
||||
|
||||
The log will be able to provide some context for what is occurring, but it is important to check with your external SMTP provider to make sure everything is properly configured.
|
||||
|
||||
## Step 3: Checking email templates
|
||||
### Step 3: Checking email templates
|
||||
|
||||
Incomplete or incorrect email templates can also cause 500 errors. If your templates have unclosed variable tags or HTML elements, or use forbidden characters, this might be the issue.
|
||||
|
||||
|
||||
@@ -81,8 +81,7 @@ chmod +x sync_supabase_config.sh
|
||||
size="medium"
|
||||
className="text-foreground-light mt-8 mb-6"
|
||||
>
|
||||
<div className="border-b mt-3 pb-3">
|
||||
<AccordionItem
|
||||
<AccordionItem
|
||||
header="Config sync script"
|
||||
id="config-sync-script"
|
||||
>
|
||||
@@ -280,7 +279,6 @@ echo "Done. Configs saved to ${OUTDIR}/"
|
||||
|
||||
</AccordionItem>
|
||||
|
||||
</div>
|
||||
</Accordion>
|
||||
|
||||
The script saves both source and target configs to a local `config_sync_<timestamp>/` directory so you can review exactly what changed. Use `--dry-run` to preview differences without applying them.
|
||||
|
||||
+3
-3
@@ -10,13 +10,13 @@ database_id = "fb1cbd42-e172-44b2-af2b-fda5aecde5c2"
|
||||
cli = [ "supabase-inspect-db" ]
|
||||
---
|
||||
|
||||
# Optimizing your database
|
||||
## Optimizing your database
|
||||
|
||||
This is an intermediate and actionable guide for Postgres optimization within the Supabase ecosystem.
|
||||
|
||||
> Consider checking out [Index_advisor](/docs/guides/database/extensions/index_advisor) and the [performance advisor](/dashboard/project/_/database/performance-advisor) now available in the Dashboard!
|
||||
|
||||
## Installing Supabase Grafana
|
||||
### Installing Supabase Grafana
|
||||
|
||||
Supabase has an [open-source Grafana Repo](https://github.com/supabase/supabase-grafana) that displays real-time metrics of your database. Although the [Observability Dashboard](/dashboard/project/_/observability) provides similar metrics, it averages the data by the hour or day. Having visibility over how your database responds to changes helps to ensure that the database is not stressed by the index-building process.
|
||||
|
||||
@@ -25,7 +25,7 @@ _Visual of Grafana Dashboard_
|
||||
|
||||
It can be run locally within Docker or can be deployed for free to fly.io. Installation instructions can be found in [Supabase's metrics docs](/docs/guides/telemetry/metrics/grafana-self-hosted)
|
||||
|
||||
## Query optimization through indexes
|
||||
### Query optimization through indexes
|
||||
|
||||
Disk (storage) is relatively slow compared to memory, so Postgres will take frequently accessed data and cache it in memory for fast access.
|
||||
|
||||
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
---
|
||||
title = "Storage error: 403 Forbidden: 'new row violates row-level security policy' on upload"
|
||||
date_created = "2026-06-19T09:05:11+00:00"
|
||||
topics = [ "auth", "database", "storage" ]
|
||||
keywords = []
|
||||
[[errors]]
|
||||
http_status_code = 403
|
||||
message = "Forbidden"
|
||||
|
||||
---
|
||||
|
||||
If you are observing a 403 Forbidden error with the message 'new row violates row-level security policy' when uploading files, it typically indicates that the database cannot return the metadata for the newly created object. This can happen even if your INSERT policies are correctly defined and the user's JWT is valid.
|
||||
|
||||
**Why Does This Happen?**
|
||||
The Supabase Storage API executes an `INSERT` operation followed by a `RETURNING *` clause to provide object details back to the client. If a **SELECT** RLS policy is missing or does not cover the object being uploaded, the database is unable to return the row metadata. This results in a policy violation that causes the entire transaction to fail.
|
||||
|
||||
**How to Resolve:**
|
||||
Add a **SELECT** RLS policy to the `example_schema.example_table` (specifically `storage.objects`) that mirrors your `INSERT` requirements. Ensure the policy allows the authenticated user to read the record they are currently creating.
|
||||
|
||||
- For example, if your INSERT policy is restricted to `auth.uid()`, your SELECT policy must also permit access based on `auth.uid()` or the specific bucket and path.
|
||||
|
||||
You can manage your RLS policies via the [Dashboard](/dashboard/project/_/auth/policies) or the [SQL editor](/dashboard/project/_/sql/new).
|
||||
+8
-8
@@ -7,18 +7,18 @@ keywords = [ "ipv4", "ipv6", "network", "compatibility", "address" ]
|
||||
database_id = "f27145c7-0ff5-4621-a364-5d5704bce0ff"
|
||||
---
|
||||
|
||||
# Network compatibility with your Supabase database
|
||||
## Network compatibility with your Supabase database
|
||||
|
||||
The internet uses a system called the Internet Protocol (IP) to route communication between devices. There are two main versions:
|
||||
|
||||
- **IPv4**: Introduced in 1980, it's the original version.
|
||||
- **IPv6**: Launched in 1999, it offers a much larger address space and is the preferred future-proof option.
|
||||
|
||||
### Supabase and IPv6:
|
||||
#### Supabase and IPv6
|
||||
|
||||
All Supabase databases provide a direct connection string that maps to an IPv6 address.
|
||||
|
||||
### Working with IPv6 incompatible hosts:
|
||||
#### Working with IPv6 incompatible hosts
|
||||
|
||||
Here are your options if your server platform doesn't support IPv6:
|
||||
|
||||
@@ -28,7 +28,7 @@ Here are your options if your server platform doesn't support IPv6:
|
||||
|
||||
> Note: the IPv4 Add-On costs <Price price="0.0055" /> an hour, which equates to ~<Price price="4.00" /> if left on for a full month (~720 hours)
|
||||
|
||||
### Checking IPv6 support:
|
||||
#### Checking IPv6 support
|
||||
|
||||
The majority of services are IPv6 compatible. However, there are a few prominent ones that only accept IPv4 connections:
|
||||
|
||||
@@ -45,7 +45,7 @@ curl -6 https://ifconfig.co/ip
|
||||
|
||||
If the command returns an IPv6 address, the network is IPv6 compatible.
|
||||
|
||||
### Finding your database's IP address:
|
||||
#### Finding your database's IP address
|
||||
|
||||
To determine your current IP address, you can use an IP address [lookup website](https://whatismyipaddress.com/hostname-ip) or the terminal command:
|
||||
|
||||
@@ -57,7 +57,7 @@ This command queries the domain name servers to find the IP address of the given
|
||||
|
||||
Example IPv6 Address: `2a05:d014:1c06:5f0c:d7a9:8616:bee2:30df`
|
||||
|
||||
### Identifying your connections:
|
||||
#### Identifying your connections
|
||||
|
||||
The pooler and direct connection strings can be found on the dashboard by clicking [Connect](/dashboard/project/_?showConnect=true).
|
||||
|
||||
@@ -68,14 +68,14 @@ The pooler and direct connection strings can be found on the dashboard by clicki
|
||||
postgresql://postgres:[YOUR-PASSWORD]@db.ajrbwkcuthywfihaarmflo.supabase.co:5432/postgres
|
||||
```
|
||||
|
||||
#### Supavisor in transaction mode (port 6543)
|
||||
##### Supavisor in transaction mode (port 6543)
|
||||
|
||||
```sh
|
||||
# Example transaction string
|
||||
postgresql://postgres.ajrbwkcuthywddfihrmflo:[YOUR-PASSWORD]@aws-0-us-east-1.pooler.supabase.com:6543/postgres
|
||||
```
|
||||
|
||||
#### Supavisor in session mode (port 5432)
|
||||
##### Supavisor in session mode (port 5432)
|
||||
|
||||
```sh
|
||||
# Example session string
|
||||
|
||||
@@ -0,0 +1,293 @@
|
||||
{
|
||||
"anonymous_provider_disabled": {
|
||||
"description": "Anonymous sign-ins are disabled."
|
||||
},
|
||||
"bad_code_verifier": {
|
||||
"description": "Returned from the PKCE flow where the provided code verifier does not match the expected one. Indicates a bug in the implementation of the client library."
|
||||
},
|
||||
"bad_json": {
|
||||
"description": "Usually used when the HTTP body of the request is not valid JSON."
|
||||
},
|
||||
"bad_jwt": {
|
||||
"description": "JWT sent in the Authorization header is not valid."
|
||||
},
|
||||
"bad_oauth_callback": {
|
||||
"description": "OAuth callback from provider to Auth does not have all the required attributes (state). Indicates an issue with the OAuth provider or client library implementation."
|
||||
},
|
||||
"bad_oauth_state": {
|
||||
"description": "OAuth state (data echoed back by the OAuth provider to Supabase Auth) is not in the correct format. Indicates an issue with the OAuth provider integration."
|
||||
},
|
||||
"captcha_failed": {
|
||||
"description": "CAPTCHA challenge could not be verified with the CAPTCHA provider. Check your CAPTCHA integration."
|
||||
},
|
||||
"conflict": {
|
||||
"description": "General database conflict, such as concurrent requests on resources that should not be modified concurrently. Can often occur when you have too many session refresh requests firing off at the same time for a user. Check your app for concurrency issues, and if detected, back off exponentially."
|
||||
},
|
||||
"email_address_invalid": {
|
||||
"description": "Example and test domains are currently not supported. Use a different email address."
|
||||
},
|
||||
"email_address_not_authorized": {
|
||||
"description": "Email sending is not allowed for this address as your project is using the default SMTP service. Emails can only be sent to members in your Supabase organization. If you want to send emails to others, set up a custom SMTP provider.",
|
||||
"references": [
|
||||
{
|
||||
"href": "https://supabase.com/docs/guides/auth/auth-smtp",
|
||||
"description": "Setting up a custom SMTP provider"
|
||||
}
|
||||
]
|
||||
},
|
||||
"email_conflict_identity_not_deletable": {
|
||||
"description": "Unlinking this identity causes the user's account to change to an email address which is already used by another user account. Indicates an issue where the user has two different accounts using different primary email addresses. You may need to migrate user data to one of their accounts in this case."
|
||||
},
|
||||
"email_exists": {
|
||||
"description": "Email address already exists in the system."
|
||||
},
|
||||
"email_not_confirmed": {
|
||||
"description": "Signing in is not allowed for this user as the email address is not confirmed."
|
||||
},
|
||||
"email_provider_disabled": {
|
||||
"description": "Signups are disabled for email and password."
|
||||
},
|
||||
"flow_state_expired": {
|
||||
"description": "PKCE flow state to which the API request relates has expired. Ask the user to sign in again."
|
||||
},
|
||||
"flow_state_not_found": {
|
||||
"description": "PKCE flow state to which the API request relates no longer exists. Flow states expire after a while and are progressively cleaned up, which can cause this error. Retried requests can cause this error, as the previous request likely destroyed the flow state. Ask the user to sign in again."
|
||||
},
|
||||
"hook_payload_invalid_content_type": {
|
||||
"description": "Payload from Auth does not have a valid Content-Type header."
|
||||
},
|
||||
"hook_payload_over_size_limit": {
|
||||
"description": "Payload from Auth exceeds maximum size limit."
|
||||
},
|
||||
"hook_timeout": {
|
||||
"description": "Unable to reach hook within maximum time allocated."
|
||||
},
|
||||
"hook_timeout_after_retry": {
|
||||
"description": "Unable to reach hook after maximum number of retries."
|
||||
},
|
||||
"identity_already_exists": {
|
||||
"description": "The identity to which the API relates is already linked to a user."
|
||||
},
|
||||
"identity_not_found": {
|
||||
"description": "Identity to which the API call relates does not exist, such as when an identity is unlinked or deleted."
|
||||
},
|
||||
"insufficient_aal": {
|
||||
"description": "To call this API, the user must have a higher Authenticator Assurance Level. To resolve, ask the user to solve an MFA challenge.",
|
||||
"references": [
|
||||
{
|
||||
"href": "https://supabase.com/docs/guides/auth/auth-mfa",
|
||||
"description": "MFA"
|
||||
}
|
||||
]
|
||||
},
|
||||
"invite_not_found": {
|
||||
"description": "Invite is expired or already used."
|
||||
},
|
||||
"invalid_credentials": {
|
||||
"description": "Login credentials or grant type not recognized."
|
||||
},
|
||||
"manual_linking_disabled": {
|
||||
"description": "Calling the supabase.auth.linkUser() and related APIs is not enabled on the Auth server."
|
||||
},
|
||||
"mfa_challenge_expired": {
|
||||
"description": "Responding to an MFA challenge should happen within a fixed time period. Request a new challenge when encountering this error."
|
||||
},
|
||||
"mfa_factor_name_conflict": {
|
||||
"description": "MFA factors for a single user should not have the same friendly name."
|
||||
},
|
||||
"mfa_factor_not_found": {
|
||||
"description": "MFA factor no longer exists."
|
||||
},
|
||||
"mfa_ip_address_mismatch": {
|
||||
"description": "The enrollment process for MFA factors must begin and end with the same IP address."
|
||||
},
|
||||
"mfa_phone_enroll_not_enabled": {
|
||||
"description": "Enrollment of MFA Phone factors is disabled."
|
||||
},
|
||||
"mfa_phone_verify_not_enabled": {
|
||||
"description": "Login via Phone factors and verification of new Phone factors is disabled."
|
||||
},
|
||||
"mfa_totp_enroll_not_enabled": {
|
||||
"description": "Enrollment of MFA TOTP factors is disabled."
|
||||
},
|
||||
"mfa_totp_verify_not_enabled": {
|
||||
"description": "Login via TOTP factors and verification of new TOTP factors is disabled."
|
||||
},
|
||||
"mfa_verification_failed": {
|
||||
"description": "MFA challenge could not be verified -- wrong TOTP code."
|
||||
},
|
||||
"mfa_verification_rejected": {
|
||||
"description": "Further MFA verification is rejected. Only returned if the MFA verification attempt hook returns a reject decision.",
|
||||
"references": [
|
||||
{
|
||||
"href": "https://supabase.com/docs/guides/auth/auth-hooks/mfa-verification-hook",
|
||||
"description": "MFA verification hook"
|
||||
}
|
||||
]
|
||||
},
|
||||
"mfa_verified_factor_exists": {
|
||||
"description": "Verified phone factor already exists for a user. Unenroll existing verified phone factor to continue."
|
||||
},
|
||||
"mfa_web_authn_enroll_not_enabled": {
|
||||
"description": "Enrollment of MFA Web Authn factors is disabled."
|
||||
},
|
||||
"mfa_web_authn_verify_not_enabled": {
|
||||
"description": "Login via WebAuthn factors and verification of new WebAuthn factors is disabled."
|
||||
},
|
||||
"no_authorization": {
|
||||
"description": "This HTTP request requires an Authorization header, which is not provided."
|
||||
},
|
||||
"not_admin": {
|
||||
"description": "User accessing the API is not admin, i.e. the JWT does not contain a role claim that identifies them as an admin of the Auth server."
|
||||
},
|
||||
"oauth_provider_not_supported": {
|
||||
"description": "Using an OAuth provider which is disabled on the Auth server."
|
||||
},
|
||||
"otp_disabled": {
|
||||
"description": "Sign in with OTPs (magic link, email OTP) is disabled. Check your server's configuration."
|
||||
},
|
||||
"otp_expired": {
|
||||
"description": "OTP code for this sign-in has expired. Ask the user to sign in again."
|
||||
},
|
||||
"over_email_send_rate_limit": {
|
||||
"description": "Too many emails have been sent to this email address. Ask the user to wait a while before trying again."
|
||||
},
|
||||
"over_request_rate_limit": {
|
||||
"description": "Too many requests have been sent by this client (IP address). Ask the user to try again in a few minutes. Sometimes can indicate a bug in your application that mistakenly sends out too many requests (such as a badly written useEffect React hook).",
|
||||
"references": [
|
||||
{
|
||||
"href": "https://react.dev/reference/react/useEffect",
|
||||
"description": "React useEffect hook"
|
||||
}
|
||||
]
|
||||
},
|
||||
"over_sms_send_rate_limit": {
|
||||
"description": "Too many SMS messages have been sent to this phone number. Ask the user to wait a while before trying again."
|
||||
},
|
||||
"phone_exists": {
|
||||
"description": "Phone number already exists in the system."
|
||||
},
|
||||
"phone_not_confirmed": {
|
||||
"description": "Signing in is not allowed for this user as the phone number is not confirmed."
|
||||
},
|
||||
"phone_provider_disabled": {
|
||||
"description": "Signups are disabled for phone and password."
|
||||
},
|
||||
"provider_disabled": {
|
||||
"description": "OAuth provider is disabled for use. Check your server's configuration."
|
||||
},
|
||||
"provider_email_needs_verification": {
|
||||
"description": "Not all OAuth providers verify their user's email address. Supabase Auth requires emails to be verified, so this error is sent out when a verification email is sent after completing the OAuth flow."
|
||||
},
|
||||
"reauthentication_needed": {
|
||||
"description": "A user needs to reauthenticate to change their password. Ask the user to reauthenticate by calling the supabase.auth.reauthenticate() API."
|
||||
},
|
||||
"reauthentication_not_valid": {
|
||||
"description": "Verifying a reauthentication failed, the code is incorrect. Ask the user to enter a new code."
|
||||
},
|
||||
"refresh_token_not_found": {
|
||||
"description": "Session containing the refresh token not found."
|
||||
},
|
||||
"refresh_token_already_used": {
|
||||
"description": "Refresh token has been revoked and falls outside the refresh token reuse interval. See the documentation on sessions for further information.",
|
||||
"references": [
|
||||
{
|
||||
"href": "https://supabase.com/docs/guides/auth/sessions",
|
||||
"description": "Auth sessions"
|
||||
}
|
||||
]
|
||||
},
|
||||
"request_timeout": {
|
||||
"description": "Processing the request took too long. Retry the request."
|
||||
},
|
||||
"same_password": {
|
||||
"description": "A user that is updating their password must use a different password than the one currently used."
|
||||
},
|
||||
"saml_assertion_no_email": {
|
||||
"description": "SAML assertion (user information) was received after sign in, but no email address was found in it, which is required. Check the provider's attribute mapping and/or configuration."
|
||||
},
|
||||
"saml_assertion_no_user_id": {
|
||||
"description": "SAML assertion (user information) was received after sign in, but a user ID (called NameID) was not found in it, which is required. Check the SAML identity provider's configuration."
|
||||
},
|
||||
"saml_entity_id_mismatch": {
|
||||
"description": "(Admin API.) Updating the SAML metadata for a SAML identity provider is not possible, as the entity ID in the update does not match the entity ID in the database. This is equivalent to creating a new identity provider, and you should do that instead."
|
||||
},
|
||||
"saml_idp_already_exists": {
|
||||
"description": "(Admin API.) Adding a SAML identity provider that is already added."
|
||||
},
|
||||
"saml_idp_not_found": {
|
||||
"description": "SAML identity provider not found. Most often returned after IdP-initiated sign-in with an unregistered SAML identity provider in Supabase Auth."
|
||||
},
|
||||
"saml_metadata_fetch_failed": {
|
||||
"description": "(Admin API.) Adding or updating a SAML provider failed as its metadata could not be fetched from the provided URL."
|
||||
},
|
||||
"saml_provider_disabled": {
|
||||
"description": "Using Enterprise SSO with SAML 2.0 is not enabled on the Auth server.",
|
||||
"references": [
|
||||
{
|
||||
"href": "https://supabase.com/docs/guides/auth/enterprise-sso/auth-sso-saml",
|
||||
"description": "Enterprise SSO"
|
||||
}
|
||||
]
|
||||
},
|
||||
"saml_relay_state_expired": {
|
||||
"description": "SAML relay state is an object that tracks the progress of a supabase.auth.signInWithSSO() request. The SAML identity provider should respond after a fixed amount of time, after which this error is shown. Ask the user to sign in again."
|
||||
},
|
||||
"saml_relay_state_not_found": {
|
||||
"description": "SAML relay states are progressively cleaned up after they expire, which can cause this error. Ask the user to sign in again."
|
||||
},
|
||||
"session_expired": {
|
||||
"description": "Session to which the API request relates has expired. This can occur if an inactivity timeout is configured, or the session entry has exceeded the configured timebox value. See the documentation on sessions for more information.",
|
||||
"references": [
|
||||
{
|
||||
"href": "https://supabase.com/docs/guides/auth/sessions",
|
||||
"description": "Auth sessions"
|
||||
}
|
||||
]
|
||||
},
|
||||
"session_not_found": {
|
||||
"description": "Session to which the API request relates no longer exists. This can occur if the user has signed out, or the session entry in the database was deleted in some other way."
|
||||
},
|
||||
"signup_disabled": {
|
||||
"description": "Sign ups (new account creation) are disabled on the server."
|
||||
},
|
||||
"single_identity_not_deletable": {
|
||||
"description": "Every user must have at least one identity attached to it, so deleting (unlinking) an identity is not allowed if it's the only one for the user."
|
||||
},
|
||||
"sms_send_failed": {
|
||||
"description": "Sending an SMS message failed. Check your SMS provider configuration."
|
||||
},
|
||||
"sso_domain_already_exists": {
|
||||
"description": "(Admin API.) Only one SSO domain can be registered per SSO identity provider."
|
||||
},
|
||||
"sso_provider_not_found": {
|
||||
"description": "SSO provider not found. Check the arguments in supabase.auth.signInWithSSO()."
|
||||
},
|
||||
"too_many_enrolled_mfa_factors": {
|
||||
"description": "A user can only have a fixed number of enrolled MFA factors."
|
||||
},
|
||||
"unexpected_audience": {
|
||||
"description": "(Deprecated feature not available via Supabase client libraries.) The request's X-JWT-AUD claim does not match the JWT's audience."
|
||||
},
|
||||
"unexpected_failure": {
|
||||
"description": "Auth service is degraded or a bug is present, without a specific reason."
|
||||
},
|
||||
"user_already_exists": {
|
||||
"description": "User with this information (email address, phone number) cannot be created again as it already exists."
|
||||
},
|
||||
"user_banned": {
|
||||
"description": "User to which the API request relates has a banned_until property which is still active. No further API requests should be attempted until this field is cleared."
|
||||
},
|
||||
"user_not_found": {
|
||||
"description": "User to which the API request relates no longer exists."
|
||||
},
|
||||
"user_sso_managed": {
|
||||
"description": "When a user comes from SSO, certain fields of the user cannot be updated (like email)."
|
||||
},
|
||||
"validation_failed": {
|
||||
"description": "Provided parameters are not in the expected format."
|
||||
},
|
||||
"weak_password": {
|
||||
"description": "User is signing up or changing their password without meeting the password strength criteria. Use the AuthWeakPasswordError class to access more information about what they need to do to make the password pass."
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,213 @@
|
||||
{
|
||||
"TopicNameRequired": {
|
||||
"description": "You are trying to use Realtime without a topic name set."
|
||||
},
|
||||
"RealtimeDisabledForConfiguration": {
|
||||
"description": "The configuration provided to Realtime on connect will not be able to provide you any Postgres Changes.",
|
||||
"resolution": "Verify your configuration on channel startup as you might not have your tables properly registered."
|
||||
},
|
||||
"TenantNotFound": {
|
||||
"description": "The tenant you are trying to connect to does not exist.",
|
||||
"resolution": "Verify the tenant name you are trying to connect to exists in the realtime.tenants table."
|
||||
},
|
||||
"ErrorConnectingToWebsocket": {
|
||||
"description": "Error when trying to connect to the WebSocket server.",
|
||||
"resolution": "Verify user information on connect."
|
||||
},
|
||||
"ErrorAuthorizingWebsocket": {
|
||||
"description": "Error when trying to authorize the WebSocket connection.",
|
||||
"resolution": "Verify user information on connect."
|
||||
},
|
||||
"TableHasSpacesInName": {
|
||||
"description": "The table you are trying to listen to has spaces in its name which we are unable to support.",
|
||||
"resolution": "Change the table name to not have spaces in it."
|
||||
},
|
||||
"UnableToDeleteTenant": {
|
||||
"description": "Error when trying to delete a tenant."
|
||||
},
|
||||
"UnableToSetPolicies": {
|
||||
"description": "Error when setting up Authorization Policies."
|
||||
},
|
||||
"UnableCheckoutConnection": {
|
||||
"description": "Error when trying to checkout a connection from the tenant pool."
|
||||
},
|
||||
"UnableToSubscribeToPostgres": {
|
||||
"description": "Error when trying to subscribe to Postgres changes."
|
||||
},
|
||||
"ReconnectSubscribeToPostgres": {
|
||||
"description": "Postgres changes still waiting to be subscribed."
|
||||
},
|
||||
"ChannelRateLimitReached": {
|
||||
"description": "The number of channels you can create has reached its limit."
|
||||
},
|
||||
"ConnectionRateLimitReached": {
|
||||
"description": "The number of connected clients has reached its limit."
|
||||
},
|
||||
"ClientJoinRateLimitReached": {
|
||||
"description": "The rate of joins per second from your clients has reached the channel limits."
|
||||
},
|
||||
"RealtimeDisabledForTenant": {
|
||||
"description": "Realtime has been disabled for the tenant.",
|
||||
"resolution": "Your project may have been suspended for exceeding usage quotas. Contact support with your project reference ID and a description of your Realtime use case.",
|
||||
"references": [
|
||||
{
|
||||
"href": "https://supabase.com/docs/troubleshooting/realtime-project-suspended-for-exceeding-quotas",
|
||||
"description": "Troubleshooting guide for suspended projects"
|
||||
}
|
||||
]
|
||||
},
|
||||
"UnableToConnectToTenantDatabase": {
|
||||
"description": "Realtime was not able to connect to the tenant's database."
|
||||
},
|
||||
"DatabaseLackOfConnections": {
|
||||
"description": "Realtime was not able to connect to the tenant's database due to not having enough available connections.",
|
||||
"resolution": "Verify your database connection limits.",
|
||||
"references": [
|
||||
{
|
||||
"href": "https://supabase.com/docs/guides/database/connection-management",
|
||||
"description": "Connection management guide"
|
||||
}
|
||||
]
|
||||
},
|
||||
"RealtimeNodeDisconnected": {
|
||||
"description": "Realtime is a distributed application and this means that one the system is unable to communicate with one of the distributed nodes."
|
||||
},
|
||||
"MigrationsFailedToRun": {
|
||||
"description": "Error when running the migrations against the Tenant database that are required by Realtime."
|
||||
},
|
||||
"StartListenAndReplicationFailed": {
|
||||
"description": "Error when starting the replication and listening of errors for database broadcasting."
|
||||
},
|
||||
"ReplicationMaxWalSendersReached": {
|
||||
"description": "Maximum number of WAL senders reached in tenant database.",
|
||||
"references": [
|
||||
{
|
||||
"href": "https://supabase.com/docs/guides/database/custom-postgres-config#cli-configurable-settings",
|
||||
"description": "Configuring max WAL senders"
|
||||
}
|
||||
]
|
||||
},
|
||||
"MigrationCheckFailed": {
|
||||
"description": "Check to see if we require to run migrations fails."
|
||||
},
|
||||
"PartitionCreationFailed": {
|
||||
"description": "Error when creating partitions for realtime.messages."
|
||||
},
|
||||
"ErrorStartingPostgresCDCStream": {
|
||||
"description": "Error when starting the Postgres CDC stream which is used for Postgres Changes."
|
||||
},
|
||||
"UnknownDataProcessed": {
|
||||
"description": "An unknown data type was processed by the Realtime system."
|
||||
},
|
||||
"ErrorStartingPostgresCDC": {
|
||||
"description": "Error when starting the Postgres CDC extension which is used for Postgres Changes."
|
||||
},
|
||||
"ReplicationSlotBeingUsed": {
|
||||
"description": "The replication slot is being used by another transaction."
|
||||
},
|
||||
"PoolingReplicationPreparationError": {
|
||||
"description": "Error when preparing the replication slot."
|
||||
},
|
||||
"PoolingReplicationError": {
|
||||
"description": "Error when pooling the replication slot."
|
||||
},
|
||||
"SubscriptionDeletionFailed": {
|
||||
"description": "Error when trying to delete a subscription for postgres changes."
|
||||
},
|
||||
"UnableToDeletePhantomSubscriptions": {
|
||||
"description": "Error when trying to delete subscriptions that are no longer being used."
|
||||
},
|
||||
"UnableToCheckProcessesOnRemoteNode": {
|
||||
"description": "Error when trying to check the processes on a remote node."
|
||||
},
|
||||
"UnableToCreateCounter": {
|
||||
"description": "Error when trying to create a counter to track rate limits for a tenant."
|
||||
},
|
||||
"UnableToIncrementCounter": {
|
||||
"description": "Error when trying to increment a counter to track rate limits for a tenant."
|
||||
},
|
||||
"UnableToDecrementCounter": {
|
||||
"description": "Error when trying to decrement a counter to track rate limits for a tenant."
|
||||
},
|
||||
"UnableToUpdateCounter": {
|
||||
"description": "Error when trying to update a counter to track rate limits for a tenant."
|
||||
},
|
||||
"UnableToFindCounter": {
|
||||
"description": "Error when trying to find a counter to track rate limits for a tenant."
|
||||
},
|
||||
"UnhandledProcessMessage": {
|
||||
"description": "Unhandled message received by a Realtime process."
|
||||
},
|
||||
"UnableToTrackPresence": {
|
||||
"description": "Error when handling track presence for this socket."
|
||||
},
|
||||
"UnknownPresenceEvent": {
|
||||
"description": "Presence event type not recognized by service."
|
||||
},
|
||||
"IncreaseConnectionPool": {
|
||||
"description": "The number of connections you have set for Realtime are not enough to handle your current use case."
|
||||
},
|
||||
"RlsPolicyError": {
|
||||
"description": "Error on RLS policy used for authorization."
|
||||
},
|
||||
"ConnectionInitializing": {
|
||||
"description": "Database is initializing connection."
|
||||
},
|
||||
"DatabaseConnectionIssue": {
|
||||
"description": "Database had connection issues and connection was not able to be established."
|
||||
},
|
||||
"UnableToConnectToProject": {
|
||||
"description": "Unable to connect to Project database."
|
||||
},
|
||||
"InvalidJWTExpiration": {
|
||||
"description": "JWT exp claim value it's incorrect."
|
||||
},
|
||||
"JwtSignatureError": {
|
||||
"description": "JWT signature was not able to be validated."
|
||||
},
|
||||
"MalformedJWT": {
|
||||
"description": "Token received does not comply with the JWT format."
|
||||
},
|
||||
"Unauthorized": {
|
||||
"description": "Unauthorized access to Realtime channel."
|
||||
},
|
||||
"RealtimeRestarting": {
|
||||
"description": "Realtime is currently restarting."
|
||||
},
|
||||
"UnableToProcessListenPayload": {
|
||||
"description": "Payload sent in NOTIFY operation was not JSON parsable."
|
||||
},
|
||||
"UnableToListenToTenantDatabase": {
|
||||
"description": "Unable to LISTEN for notifications against the Tenant Database."
|
||||
},
|
||||
"UnprocessableEntity": {
|
||||
"description": "Received a HTTP request with a body that was not able to be processed by the endpoint."
|
||||
},
|
||||
"InitializingProjectConnection": {
|
||||
"description": "Connection against Tenant database is still starting."
|
||||
},
|
||||
"TimeoutOnRpcCall": {
|
||||
"description": "RPC request within the Realtime server has timed out."
|
||||
},
|
||||
"ErrorOnRpcCall": {
|
||||
"description": "Error when calling another realtime node."
|
||||
},
|
||||
"ErrorExecutingTransaction": {
|
||||
"description": "Error executing a database transaction in tenant database."
|
||||
},
|
||||
"SynInitializationError": {
|
||||
"description": "Our framework to syncronize processes has failed to properly startup a connection to the database."
|
||||
},
|
||||
"JanitorFailedToDeleteOldMessages": {
|
||||
"description": "Scheduled task for realtime.message cleanup was unable to run."
|
||||
},
|
||||
"UnableToEncodeJson": {
|
||||
"description": "An error were we are not handling correctly the response to be sent to the end user."
|
||||
},
|
||||
"UnknownErrorOnController": {
|
||||
"description": "An error we are not handling correctly was triggered on a controller."
|
||||
},
|
||||
"UnknownErrorOnChannel": {
|
||||
"description": "An error we are not handling correctly was triggered on a channel."
|
||||
}
|
||||
}
|
||||
@@ -61,6 +61,7 @@ export const TroubleshootingSchema = z
|
||||
z.enum([
|
||||
'ai',
|
||||
'ai-tools',
|
||||
'api',
|
||||
'auth',
|
||||
'branching',
|
||||
'cli',
|
||||
@@ -129,12 +130,10 @@ export async function getAllTroubleshootingEntriesInternal() {
|
||||
|
||||
const parseResult = validateTroubleshootingMetadata(frontmatter)
|
||||
if ('error' in parseResult) {
|
||||
console.error(
|
||||
`Error validating troubleshooting metadata\nEntry:%O\nError:%O`,
|
||||
frontmatter,
|
||||
parseResult.error
|
||||
throw Error(
|
||||
`Error validating troubleshooting metadata for ${filePath}`,
|
||||
{ cause: parseResult.error }
|
||||
)
|
||||
return null
|
||||
}
|
||||
|
||||
const mdxTree = fromMarkdown(content, {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import _authErrorCodes from '~/data/errorCodes/authErrorCodes.json'
|
||||
import _realtimeErrorCodes from '~/data/errorCodes/realtimeErrorCodes.json'
|
||||
import { type ErrorCodeDefinition } from '~/resources/error/errorTypes'
|
||||
import Link from 'next/link'
|
||||
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from 'ui'
|
||||
import _authErrorCodes from '~/content/errorCodes/authErrorCodes.toml'
|
||||
import _realtimeErrorCodes from '~/content/errorCodes/realtimeErrorCodes.toml'
|
||||
import { type ErrorCodeDefinition } from '~/resources/error/errorTypes'
|
||||
|
||||
const errorCodesByService = {
|
||||
auth: _authErrorCodes as Record<string, ErrorCodeDefinition>,
|
||||
|
||||
@@ -11,14 +11,21 @@ import { toMarkdown } from 'mdast-util-to-markdown'
|
||||
import { gfm } from 'micromark-extension-gfm'
|
||||
import { mdxjs } from 'micromark-extension-mdxjs'
|
||||
|
||||
import { getInternalLinkBaseUrl, prefixInternalLinks } from './internal-links'
|
||||
import { addBaseUrlPrefix } from './internal-links'
|
||||
import { Admonition } from './markdown-schema/Admonition'
|
||||
import { AuthProviders } from './markdown-schema/AuthProviders'
|
||||
import { ComputeDiskLimitsTable } from './markdown-schema/ComputeDiskLimitsTable'
|
||||
import { ErrorCodes } from './markdown-schema/ErrorCodes'
|
||||
import { Link } from './markdown-schema/Link'
|
||||
import { MetricsStackCards } from './markdown-schema/MetricsStackCards'
|
||||
import { NavData } from './markdown-schema/NavData'
|
||||
import { Panel } from './markdown-schema/Panel'
|
||||
import { Price } from './markdown-schema/Price'
|
||||
import { RealtimeLimitsEstimator } from './markdown-schema/RealtimeLimitsEstimator'
|
||||
import { RegionsList, SmartRegionsList } from './markdown-schema/RegionsList'
|
||||
import { SharedData } from './markdown-schema/SharedData'
|
||||
import { StepHike } from './markdown-schema/StepHike'
|
||||
import { TabPanel } from './markdown-schema/TabPanel'
|
||||
import { Price } from './markdown-schema/Price'
|
||||
|
||||
const PARTIALS_DIR = path.join(process.cwd(), 'content', '_partials')
|
||||
|
||||
@@ -131,23 +138,32 @@ function applySchema(parent: Parent, schema: ComponentSchema): void {
|
||||
*/
|
||||
const SCHEMA: ComponentSchema = {
|
||||
Admonition,
|
||||
AuthProviders,
|
||||
ComputeDiskLimitsTable,
|
||||
ErrorCodes,
|
||||
Link,
|
||||
Price,
|
||||
GlassPanel: Panel,
|
||||
IconPanel: Panel,
|
||||
RealtimeLimitsEstimator,
|
||||
RegionsList,
|
||||
SmartRegionsList,
|
||||
...StepHike,
|
||||
TabPanel,
|
||||
MetricsStackCards,
|
||||
NavData,
|
||||
SharedData,
|
||||
}
|
||||
|
||||
async function generateOne(filePath: string, linkBaseUrl: string): Promise<string> {
|
||||
async function generateOne(filePath: string): Promise<string> {
|
||||
const raw = await fs.readFile(filePath, 'utf8')
|
||||
const { content, data } = matter(raw)
|
||||
|
||||
const tree = parseMdx(content)
|
||||
await inlinePartials(tree)
|
||||
addBaseUrlPrefix(tree)
|
||||
applySchema(tree, SCHEMA)
|
||||
const body = prefixInternalLinks(serializeMdx(tree), linkBaseUrl)
|
||||
const body = serializeMdx(tree)
|
||||
|
||||
const headerParts: string[] = []
|
||||
if (data.title) headerParts.push(`# ${data.title}`)
|
||||
@@ -163,7 +179,6 @@ async function generateOne(filePath: string, linkBaseUrl: string): Promise<strin
|
||||
|
||||
async function generate() {
|
||||
const files = await globby(['content/guides/**/!(_)*.mdx'])
|
||||
const linkBaseUrl = getInternalLinkBaseUrl()
|
||||
let warnings = 0
|
||||
|
||||
await Promise.all(
|
||||
@@ -177,7 +192,7 @@ async function generate() {
|
||||
|
||||
let output: string
|
||||
try {
|
||||
output = await generateOne(filePath, linkBaseUrl)
|
||||
output = await generateOne(filePath)
|
||||
} catch (err) {
|
||||
warnings++
|
||||
console.warn(
|
||||
|
||||
@@ -2,8 +2,12 @@ import fs from 'node:fs/promises'
|
||||
import path from 'node:path'
|
||||
import matter from 'gray-matter'
|
||||
import yaml from 'js-yaml'
|
||||
import { fromMarkdown } from 'mdast-util-from-markdown'
|
||||
import { gfmFromMarkdown, gfmToMarkdown } from 'mdast-util-gfm'
|
||||
import { toMarkdown } from 'mdast-util-to-markdown'
|
||||
import { gfm } from 'micromark-extension-gfm'
|
||||
|
||||
import { getInternalLinkBaseUrl, prefixInternalLinks } from './internal-links'
|
||||
import { addBaseUrlPrefix } from './internal-links'
|
||||
|
||||
const GENERATED = path.join(process.cwd(), 'features/docs/generated')
|
||||
const OUT_DIR = path.join(process.cwd(), 'public/markdown/reference')
|
||||
@@ -388,7 +392,6 @@ async function generate() {
|
||||
const sharedTypeSpec = await readJson<TypeSpec>(
|
||||
path.join(process.cwd(), 'content/reference/javascript/v2/typeSpec.json')
|
||||
)
|
||||
const linkBaseUrl = getInternalLinkBaseUrl()
|
||||
|
||||
await Promise.all(
|
||||
REFERENCES.map(async (ref) => {
|
||||
@@ -407,7 +410,17 @@ async function generate() {
|
||||
output = await renderCli(ref)
|
||||
break
|
||||
}
|
||||
await fs.writeFile(path.join(OUT_DIR, ref.outFile), prefixInternalLinks(output, linkBaseUrl))
|
||||
const tree = fromMarkdown(output, {
|
||||
extensions: [gfm()],
|
||||
mdastExtensions: [gfmFromMarkdown()],
|
||||
})
|
||||
addBaseUrlPrefix(tree)
|
||||
const prefixed = toMarkdown(tree, {
|
||||
extensions: [gfmToMarkdown()],
|
||||
bullet: '-',
|
||||
listItemIndent: 'one',
|
||||
})
|
||||
await fs.writeFile(path.join(OUT_DIR, ref.outFile), prefixed)
|
||||
})
|
||||
)
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { fromMarkdown } from 'mdast-util-from-markdown'
|
||||
import { afterEach, beforeEach, describe, expect, it } from 'vitest'
|
||||
|
||||
import { getInternalLinkBaseUrl, prefixInternalLinks, withDocsBasePath } from './internal-links'
|
||||
import { addBaseUrlPrefix, getInternalLinkBaseUrl, withDocsBasePath } from './internal-links'
|
||||
|
||||
describe('withDocsBasePath', () => {
|
||||
it('prepends /docs to a root-relative href', () => {
|
||||
@@ -95,147 +96,46 @@ describe('getInternalLinkBaseUrl', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('prefixInternalLinks', () => {
|
||||
const BASE = 'https://supabase.com'
|
||||
describe('addBaseUrlPrefix', () => {
|
||||
const ORIGINAL_ENV = process.env
|
||||
|
||||
it('returns content unchanged when baseUrl is empty', () => {
|
||||
const input = 'See [Dashboard](/dashboard/foo).'
|
||||
expect(prefixInternalLinks(input, '')).toBe(input)
|
||||
beforeEach(() => {
|
||||
process.env = { ...ORIGINAL_ENV, VERCEL_ENV: 'production' }
|
||||
})
|
||||
|
||||
it('prepends baseUrl to a root-relative link', () => {
|
||||
expect(prefixInternalLinks('See [Dashboard](/dashboard/foo).', BASE)).toBe(
|
||||
'See [Dashboard](https://supabase.com/dashboard/foo).'
|
||||
)
|
||||
afterEach(() => {
|
||||
process.env = ORIGINAL_ENV
|
||||
})
|
||||
|
||||
it('rewrites multiple links on the same line', () => {
|
||||
const input = 'A [one](/a) and [two](/b/c) here.'
|
||||
expect(prefixInternalLinks(input, BASE)).toBe(
|
||||
'A [one](https://supabase.com/a) and [two](https://supabase.com/b/c) here.'
|
||||
)
|
||||
const linkUrls = (markdown: string): string[] => {
|
||||
const tree = fromMarkdown(markdown)
|
||||
addBaseUrlPrefix(tree)
|
||||
const urls: string[] = []
|
||||
const visit = (n: any) => {
|
||||
if (n.type === 'link') urls.push(n.url)
|
||||
if (Array.isArray(n.children)) n.children.forEach(visit)
|
||||
}
|
||||
visit(tree)
|
||||
return urls
|
||||
}
|
||||
|
||||
it('prepends baseUrl to root-relative link URLs', () => {
|
||||
expect(linkUrls('[home](/foo)')).toEqual(['https://supabase.com/foo'])
|
||||
})
|
||||
|
||||
it('preserves query strings and fragments', () => {
|
||||
expect(prefixInternalLinks('[link](/foo?bar=1&baz=2#section)', BASE)).toBe(
|
||||
'[link](https://supabase.com/foo?bar=1&baz=2#section)'
|
||||
)
|
||||
it('leaves absolute, anchor, and protocol-relative URLs alone', () => {
|
||||
expect(linkUrls('[a](https://x.com) [b](#h) [c](//cdn/x)')).toEqual([
|
||||
'https://x.com',
|
||||
'#h',
|
||||
'//cdn/x',
|
||||
])
|
||||
})
|
||||
|
||||
it('leaves absolute http(s) links alone', () => {
|
||||
const input = 'See [GitHub](https://github.com/supabase).'
|
||||
expect(prefixInternalLinks(input, BASE)).toBe(input)
|
||||
})
|
||||
|
||||
it('leaves anchor-only links alone', () => {
|
||||
const input = 'Jump to [section](#installation).'
|
||||
expect(prefixInternalLinks(input, BASE)).toBe(input)
|
||||
})
|
||||
|
||||
it('leaves mailto and other schemes alone', () => {
|
||||
const input = 'Email [us](mailto:team@example.com) or [call](tel:+1234).'
|
||||
expect(prefixInternalLinks(input, BASE)).toBe(input)
|
||||
})
|
||||
|
||||
it('leaves explicitly relative links (./, ../) alone', () => {
|
||||
const input = 'See [sibling](./sibling) and [parent](../parent).'
|
||||
expect(prefixInternalLinks(input, BASE)).toBe(input)
|
||||
})
|
||||
|
||||
it('leaves protocol-relative (//host) URLs alone', () => {
|
||||
const input = 'CDN [asset](//cdn.example.com/img.png).'
|
||||
expect(prefixInternalLinks(input, BASE)).toBe(input)
|
||||
})
|
||||
|
||||
it('does not rewrite image syntax', () => {
|
||||
const input = 'An image: .'
|
||||
expect(prefixInternalLinks(input, BASE)).toBe(input)
|
||||
})
|
||||
|
||||
it('rewrites a link adjacent to an image without touching the image', () => {
|
||||
expect(prefixInternalLinks(' and [home](/dashboard)', BASE)).toBe(
|
||||
' and [home](https://supabase.com/dashboard)'
|
||||
)
|
||||
it('does not rewrite image URLs', () => {
|
||||
expect(linkUrls('')).toEqual([])
|
||||
})
|
||||
|
||||
it('skips links inside fenced code blocks', () => {
|
||||
const input = [
|
||||
'Before: [yes](/touch-me).',
|
||||
'',
|
||||
'```md',
|
||||
'[ignore me](/leave-alone)',
|
||||
'```',
|
||||
'',
|
||||
'After: [also yes](/touch-me-too).',
|
||||
].join('\n')
|
||||
|
||||
expect(prefixInternalLinks(input, BASE)).toBe(
|
||||
[
|
||||
'Before: [yes](https://supabase.com/touch-me).',
|
||||
'',
|
||||
'```md',
|
||||
'[ignore me](/leave-alone)',
|
||||
'```',
|
||||
'',
|
||||
'After: [also yes](https://supabase.com/touch-me-too).',
|
||||
].join('\n')
|
||||
)
|
||||
})
|
||||
|
||||
it('handles multiple fenced code blocks correctly', () => {
|
||||
const input = [
|
||||
'[a](/a)',
|
||||
'```',
|
||||
'[skip1](/skip1)',
|
||||
'```',
|
||||
'[b](/b)',
|
||||
'```ts',
|
||||
'[skip2](/skip2)',
|
||||
'```',
|
||||
'[c](/c)',
|
||||
].join('\n')
|
||||
|
||||
expect(prefixInternalLinks(input, BASE)).toBe(
|
||||
[
|
||||
'[a](https://supabase.com/a)',
|
||||
'```',
|
||||
'[skip1](/skip1)',
|
||||
'```',
|
||||
'[b](https://supabase.com/b)',
|
||||
'```ts',
|
||||
'[skip2](/skip2)',
|
||||
'```',
|
||||
'[c](https://supabase.com/c)',
|
||||
].join('\n')
|
||||
)
|
||||
})
|
||||
|
||||
it('rewrites links with empty text', () => {
|
||||
expect(prefixInternalLinks('[](/foo)', BASE)).toBe('[](https://supabase.com/foo)')
|
||||
})
|
||||
|
||||
it('uses any baseUrl passed in, not just supabase.com', () => {
|
||||
expect(prefixInternalLinks('[x](/y)', 'https://branch-deploy.vercel.app')).toBe(
|
||||
'[x](https://branch-deploy.vercel.app/y)'
|
||||
)
|
||||
})
|
||||
|
||||
it('is a no-op when there are no matching links', () => {
|
||||
const input = '# Title\n\nJust prose, no links.'
|
||||
expect(prefixInternalLinks(input, BASE)).toBe(input)
|
||||
})
|
||||
|
||||
it('handles an unclosed code fence by leaving the unclosed portion untouched', () => {
|
||||
// A `split(/(```...```)/)` only pairs complete fences; an unclosed fence
|
||||
// means everything after it stays in the trailing prose segment. Document
|
||||
// that behavior rather than promising to parse malformed markdown.
|
||||
const input = ['[before](/before)', '```', '[inside-unclosed](/inside)'].join('\n')
|
||||
expect(prefixInternalLinks(input, BASE)).toBe(
|
||||
[
|
||||
'[before](https://supabase.com/before)',
|
||||
'```',
|
||||
'[inside-unclosed](https://supabase.com/inside)',
|
||||
].join('\n')
|
||||
)
|
||||
expect(linkUrls('```\n[x](/x)\n```\n\n[y](/y)')).toEqual(['https://supabase.com/y'])
|
||||
})
|
||||
})
|
||||
@@ -1,3 +1,6 @@
|
||||
import { Root } from 'mdast'
|
||||
import { visit } from 'unist-util-visit'
|
||||
|
||||
const DOCS_BASE_PATH = process.env.NEXT_PUBLIC_BASE_PATH || '/docs'
|
||||
|
||||
/**
|
||||
@@ -34,24 +37,14 @@ export function getInternalLinkBaseUrl(): string {
|
||||
return ''
|
||||
}
|
||||
|
||||
/**
|
||||
* Rewrite root-relative markdown links by prepending `baseUrl`:
|
||||
* `[text](/foo)` → `[text](${baseUrl}/foo)`
|
||||
*
|
||||
* Skips fenced code blocks, image syntax (``), protocol-relative
|
||||
* URLs (`//host/...`), and non-root-relative targets (`http://`, `mailto:`,
|
||||
* `#anchor`, `./`, `../`).
|
||||
*/
|
||||
export function prefixInternalLinks(content: string, baseUrl: string): string {
|
||||
if (!baseUrl) return content
|
||||
const segments = content.split(/(```[\s\S]*?```)/g)
|
||||
return segments
|
||||
.map((seg, i) => {
|
||||
if (i % 2 === 1) return seg
|
||||
return seg.replace(/(?<!!)(\[[^\]]*\])\((\/[^)\s]*)\)/g, (match, text, url) => {
|
||||
if (url.startsWith('//')) return match
|
||||
return `${text}(${baseUrl}${url})`
|
||||
})
|
||||
})
|
||||
.join('')
|
||||
export function addBaseUrlPrefix(tree: Root) {
|
||||
const baseUrl = getInternalLinkBaseUrl()
|
||||
|
||||
visit(tree, 'link', (node) => {
|
||||
if (node.url.startsWith('/') && !node.url.startsWith('//')) {
|
||||
node.url = baseUrl + node.url
|
||||
}
|
||||
})
|
||||
|
||||
return tree
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
import authProviders from '../../data/authProviders'
|
||||
import { withDocsBasePath } from '../internal-links'
|
||||
|
||||
export const AuthProviders = ({ props }: { props: Record<string, unknown> }): string => {
|
||||
const type = String(props.type ?? '')
|
||||
return authProviders
|
||||
.filter((p) => p.authType === type)
|
||||
.map((p) => `- [${p.name}](${withDocsBasePath(p.href)})`)
|
||||
.join('\n')
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
import { createRequire } from 'node:module'
|
||||
|
||||
// tsx's ESM loader can't pick up named exports from the `shared-data` package
|
||||
// (CJS, no `"type": "module"`). Load via `createRequire` for CJS interop —
|
||||
// this file only runs in the build script, never in the Next.js bundle.
|
||||
const {
|
||||
COMPUTE_BASELINE_IOPS,
|
||||
COMPUTE_BASELINE_THROUGHPUT,
|
||||
COMPUTE_DISK,
|
||||
COMPUTE_MAX_IOPS,
|
||||
COMPUTE_MAX_THROUGHPUT,
|
||||
} = createRequire(import.meta.url)('shared-data') as {
|
||||
COMPUTE_BASELINE_IOPS: Record<string, number>
|
||||
COMPUTE_BASELINE_THROUGHPUT: Record<string, number>
|
||||
COMPUTE_DISK: Record<string, { name: string }>
|
||||
COMPUTE_MAX_IOPS: Record<string, number>
|
||||
COMPUTE_MAX_THROUGHPUT: Record<string, number>
|
||||
}
|
||||
|
||||
export const ComputeDiskLimitsTable = (): string => `
|
||||
| Compute Instance | Baseline Throughput (MB/s) | Max Throughput (MB/s) | Baseline IOPS | Max IOPS |
|
||||
| --- | --- | --- | --- | --- |
|
||||
${Object.entries(COMPUTE_DISK)
|
||||
.map(
|
||||
([key, value]) =>
|
||||
`| ${value.name} | ${COMPUTE_BASELINE_THROUGHPUT[key]?.toLocaleString()} MB/s | ${COMPUTE_MAX_THROUGHPUT[key]?.toLocaleString()} MB/s | ${COMPUTE_BASELINE_IOPS[key]?.toLocaleString()} IOPS | ${COMPUTE_MAX_IOPS[key]?.toLocaleString()} IOPS |`
|
||||
)
|
||||
.join('\n')}
|
||||
`
|
||||
@@ -0,0 +1,42 @@
|
||||
import authErrorCodes from '../../data/errorCodes/authErrorCodes.json'
|
||||
import realtimeErrorCodes from '../../data/errorCodes/realtimeErrorCodes.json'
|
||||
import { type ErrorCodeDefinition } from '../../resources/error/errorTypes'
|
||||
|
||||
const errorCodesByService: Record<string, Record<string, ErrorCodeDefinition>> = {
|
||||
auth: authErrorCodes as Record<string, ErrorCodeDefinition>,
|
||||
realtime: realtimeErrorCodes as Record<string, ErrorCodeDefinition>,
|
||||
}
|
||||
|
||||
// Pipes break the surrounding table layout, so escape any that appear in cell text.
|
||||
const escapeCell = (value: string): string => value.replace(/\|/g, '\\|')
|
||||
|
||||
export const ErrorCodes = ({ props }: { props: Record<string, unknown> }): string => {
|
||||
const service = String(props.service ?? '')
|
||||
const errorCodes = errorCodesByService[service]
|
||||
if (!errorCodes) return ''
|
||||
|
||||
const entries = Object.entries(errorCodes).sort(([aCode], [bCode]) => aCode.localeCompare(bCode))
|
||||
const hasResolutions = entries.some(([, definition]) => definition.resolution)
|
||||
|
||||
const headings = ['Error code', 'Description', ...(hasResolutions ? ['Action'] : [])]
|
||||
const headerRow = `| ${headings.join(' | ')} |`
|
||||
const dividerRow = `| ${headings.map(() => '---').join(' | ')} |`
|
||||
|
||||
const rows = entries.map(([code, definition]) => {
|
||||
let description = escapeCell(definition.description)
|
||||
if (definition.references?.length) {
|
||||
const links = definition.references
|
||||
.map((reference) => `[${escapeCell(reference.description)}](${reference.href})`)
|
||||
.join(', ')
|
||||
description += ` Learn more: ${links}`
|
||||
}
|
||||
|
||||
const cells = [`\`${code}\``, description]
|
||||
if (hasResolutions) {
|
||||
cells.push(definition.resolution ? escapeCell(definition.resolution) : '')
|
||||
}
|
||||
return `| ${cells.join(' | ')} |`
|
||||
})
|
||||
|
||||
return [headerRow, dividerRow, ...rows].join('\n')
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import { navDataForMdx } from '../../components/Navigation/NavigationMenu/NavigationMenu.constants'
|
||||
import { withDocsBasePath } from '../internal-links'
|
||||
|
||||
type NavItem = { name?: string; url?: string }
|
||||
|
||||
export const NavData = ({ props }: { props: Record<string, unknown> }): string => {
|
||||
const dataset = navDataForMdx[props.data as keyof typeof navDataForMdx]
|
||||
if (!dataset) return ''
|
||||
|
||||
// Datasets are either a flat array of items or a section with an `items` list.
|
||||
const items: NavItem[] = Array.isArray(dataset) ? dataset : (dataset.items ?? [])
|
||||
return items
|
||||
.filter((item) => item.url)
|
||||
.map((item) => `- [${item.name}](${withDocsBasePath(String(item.url))})`)
|
||||
.join('\n')
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
import {
|
||||
COMPUTE_LABELS,
|
||||
THROUGHPUT_TABLE_HEADINGS,
|
||||
} from '../../components/RealtimeLimitsEstimator/RealtimeLimitsEstimator.constants'
|
||||
import throughputTable from '../../data/realtime/throughput.json'
|
||||
|
||||
type Row = {
|
||||
computeAddOn: string
|
||||
filters: boolean
|
||||
rls: boolean
|
||||
concurrency: number
|
||||
maxDBChanges: number
|
||||
maxMessagesPerClient: number
|
||||
totalMessagesPerSecond: number
|
||||
p95Latency: number
|
||||
}
|
||||
|
||||
const headerRow = `| ${THROUGHPUT_TABLE_HEADINGS.join(' | ')} |`
|
||||
const dividerRow = `| ${THROUGHPUT_TABLE_HEADINGS.map(() => '---').join(' | ')} |`
|
||||
|
||||
const renderGroup = (computeAddOn: string): string => {
|
||||
const rows = (throughputTable as Row[]).filter((l) => l.computeAddOn === computeAddOn)
|
||||
return `#### ${COMPUTE_LABELS[computeAddOn] ?? computeAddOn}
|
||||
|
||||
${headerRow}
|
||||
${dividerRow}
|
||||
${rows
|
||||
.map(
|
||||
(l) =>
|
||||
`| ${l.filters ? 'Yes' : 'No'} | ${l.rls ? 'Yes' : 'No'} | ${l.concurrency.toLocaleString()} | ${l.maxDBChanges} | ${l.maxMessagesPerClient} | ${l.totalMessagesPerSecond.toLocaleString()} | ${l.p95Latency}ms |`
|
||||
)
|
||||
.join('\n')}`
|
||||
}
|
||||
|
||||
export const RealtimeLimitsEstimator = (): string =>
|
||||
[...new Set((throughputTable as Row[]).map((l) => l.computeAddOn))].map(renderGroup).join('\n\n')
|
||||
@@ -0,0 +1,22 @@
|
||||
import { createRequire } from 'node:module'
|
||||
|
||||
// tsx's ESM loader can't pick up named exports from the `shared-data` package
|
||||
// (CJS, no `"type": "module"`). Load via `createRequire` for CJS interop —
|
||||
// this file only runs in the build script, never in the Next.js bundle.
|
||||
const req = createRequire(import.meta.url)
|
||||
const { AWS_REGIONS } = req('shared-data') as {
|
||||
AWS_REGIONS: Record<string, { displayName: string; code: string }>
|
||||
}
|
||||
const { SMART_REGION_TO_EXACT_REGION_MAP } = req('shared-data/regions') as {
|
||||
SMART_REGION_TO_EXACT_REGION_MAP: Map<string, string>
|
||||
}
|
||||
|
||||
export const RegionsList = (): string =>
|
||||
Object.values(AWS_REGIONS)
|
||||
.map((r) => `- ${r.displayName}, \`${r.code}\``)
|
||||
.join('\n')
|
||||
|
||||
export const SmartRegionsList = (): string =>
|
||||
[...SMART_REGION_TO_EXACT_REGION_MAP.entries()]
|
||||
.map(([smart, exact]) => `- ${smart}, \`${exact}\``)
|
||||
.join('\n')
|
||||
@@ -0,0 +1,46 @@
|
||||
import { createRequire } from 'node:module'
|
||||
|
||||
import { resolveSharedDataPath } from '../../components/SharedData.utils'
|
||||
|
||||
// tsx's ESM loader can't pick up named exports from the `shared-data` package
|
||||
// (CJS, no `"type": "module"`). Load via `createRequire` to use CJS interop —
|
||||
// this file only runs in the build script, never in the Next.js bundle.
|
||||
const { config, logConstants } = createRequire(import.meta.url)('shared-data')
|
||||
|
||||
type Field = { path: string; type: string }
|
||||
type Schema = { name: string; fields: Field[] }
|
||||
|
||||
const sharedData: Record<string, unknown> = { config, logConstants }
|
||||
|
||||
const renderLogConstants = (data: { schemas: Schema[] }): string =>
|
||||
data.schemas
|
||||
.map(
|
||||
(s) =>
|
||||
`#### ${s.name}\n${[...s.fields]
|
||||
.sort((a, b) => a.path.localeCompare(b.path))
|
||||
.map((f) => ` - \`${f.path}\`, \`${f.type}\``)
|
||||
.join('\n')}`
|
||||
)
|
||||
.join('\n\n')
|
||||
|
||||
export const SharedData = ({
|
||||
props,
|
||||
children,
|
||||
}: {
|
||||
props: Record<string, unknown>
|
||||
children: string
|
||||
}): string => {
|
||||
const dataset = sharedData[String(props.data ?? '')]
|
||||
if (!dataset) return children
|
||||
|
||||
// String-path pattern: `<SharedData data="config">a.b.c</SharedData>`.
|
||||
const value = resolveSharedDataPath(dataset, children.trim())
|
||||
if (value != null) return String(value)
|
||||
|
||||
// Render-function pattern: `<SharedData data="logConstants">{(d) => …}`.
|
||||
// The schema walker strips the MDX expression children before this handler
|
||||
// runs, and we can't evaluate the function statically anyway — hardcode the
|
||||
// markdown for the only dataset that uses this form today.
|
||||
if (props.data === 'logConstants') return renderLogConstants(dataset as { schemas: Schema[] })
|
||||
return ''
|
||||
}
|
||||
Loaded 100 of 426 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user