diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index b7074fd4b1a..5b3982ca695 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,10 +1,20 @@ /packages/shared-data/pricing.ts @roryw10 @kevcodez /packages/shared-data/plans.ts @roryw10 @kevcodez +/packages/common/telemetry-constants.ts @4L3k51 @loong @pamelachia /apps/studio/ @supabase/Dashboard +# temporary until we've ironed out all the problems with sync workflow +/apps/docs/content/troubleshooting/ @charislam + /apps/www/ @supabase/marketing /apps/www/public/images/blog @supabase/marketing /apps/www/lib/redirects.js -/docker/ @supabase/cli +/docker/ @supabase/dev-workflows + +/apps/studio/next.config.js @supabase/security +/apps/studio/components/interfaces/Billing/Payment @supabase/security +/apps/studio/components/interfaces/Organization/BillingSettings/ @supabase/security +/apps/studio/components/interfaces/Organization/Documents/ @supabase/security +/apps/studio/pages/new/index.tsx @supabase/security diff --git a/.github/workflows/ai-tests.yml b/.github/workflows/ai-tests.yml index 357b2600c32..64314d44fe7 100644 --- a/.github/workflows/ai-tests.yml +++ b/.github/workflows/ai-tests.yml @@ -33,14 +33,18 @@ jobs: with: sparse-checkout: | packages + - uses: pnpm/action-setup@v4 + name: Install pnpm + with: + run_install: false - name: Use Node.js uses: actions/setup-node@v4 with: node-version-file: '.nvmrc' - cache: 'npm' + cache: 'pnpm' - name: Install deps - run: npm ci + run: pnpm i - name: Type check - run: npm run typecheck + run: pnpm run typecheck - name: Run tests - run: npm run test + run: pnpm run test diff --git a/.github/workflows/auto-label-issues.yml b/.github/workflows/auto-label-issues.yml index d5b441349a4..59dfb2f88c3 100644 --- a/.github/workflows/auto-label-issues.yml +++ b/.github/workflows/auto-label-issues.yml @@ -42,4 +42,31 @@ jobs: run: | echo "Applying triage label for new issue" gh issue edit "$NUMBER" --add-label "$LABELS" - + spam-detection: + runs-on: ubuntu-latest + permissions: + issues: write + steps: + - name: Check GitHub Issue for spam + env: + POST_URL: ${{ secrets.POST_URL }} + BEARER_TOKEN: ${{ secrets.BEARER_TOKEN }} + NUMBER: ${{ github.event.issue.number }} + run: | + RESPONSE=$(curl -s -X POST "$POST_URL" \ + -H "Authorization: Bearer $BEARER_TOKEN" \ + -H "Content-Type: application/json" \ + -d '{"issue_id": $NUMBER}') + echo "spam_response=$RESPONSE" >> $GITHUB_OUTPUT + - name: Use spam detector output to label issue + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GH_REPO: ${{ github.repository }} + NUMBER: ${{ github.event.issue.number }} + LABEL: flagged + run: | + IS_SPAM=$(echo "$SPAM_RESPONSE" | jq -r '.spam') + if [ "$IS_SPAM" == "true" ]; then + echo "Applying flagged label for new issue suspected of spam" + gh issue edit "$NUMBER" --add-label "$LABEL" + fi diff --git a/.github/workflows/autofix_linters.yml b/.github/workflows/autofix_linters.yml index fa02d5c9f8d..548b97912d5 100644 --- a/.github/workflows/autofix_linters.yml +++ b/.github/workflows/autofix_linters.yml @@ -18,20 +18,25 @@ jobs: runs-on: ubuntu-latest if: ${{ github.event_name == 'pull_request' && (github.event.label.name == 'autofix') }} steps: - - uses: actions/checkout@v4 with: sparse-checkout: apps + ref: ${{ github.head_ref }} + + - uses: pnpm/action-setup@v4 + name: Install pnpm + with: + run_install: false - uses: actions/setup-node@v4 with: node-version-file: '.nvmrc' - name: Install required dependencies - run: npm ci + run: pnpm i - name: Run Prettier in fix mode - run: npm run format + run: pnpm run format - name: Commit changes and push to existing branch run: | @@ -39,7 +44,7 @@ jobs: git config --global user.email 'github-tidy-bot@supabase.com' if [[ `git status --porcelain` ]]; then echo "[bot] Changes detected, committing." - + echo "[bot] Running in non-squash mode." git commit -am "ci: Autofix updates from GitHub workflow" git push diff --git a/.github/workflows/avoid-typos.yml b/.github/workflows/avoid-typos.yml index 8b4f6acaf44..01f98f008b9 100644 --- a/.github/workflows/avoid-typos.yml +++ b/.github/workflows/avoid-typos.yml @@ -25,7 +25,7 @@ jobs: exclude: | *.css **/package.json - **/package-lock.json + **/pnpm-lock.yaml ./.git/* *.ipynb ./i18n/README.*.md diff --git a/.github/workflows/config-preview.yaml b/.github/workflows/config-preview.yaml deleted file mode 100644 index cca3c2d7de3..00000000000 --- a/.github/workflows/config-preview.yaml +++ /dev/null @@ -1,63 +0,0 @@ -name: Config (Preview) - -on: - pull_request: - types: - - opened - - reopened - - synchronize - branches: - - master - paths: - - "supabase/**" - workflow_dispatch: - -jobs: - wait: - runs-on: ubuntu-latest - outputs: - status: ${{ steps.check.outputs.conclusion }} - steps: - - uses: fountainhead/action-wait-for-check@v1.2.0 - id: check - with: - checkName: Supabase - ref: ${{ github.event.pull_request.head.sha }} - token: ${{ secrets.GITHUB_TOKEN }} - - apply: - needs: - - wait - if: ${{ needs.wait.outputs.status == 'success' }} - runs-on: ubuntu-latest - env: - SUPABASE_ACCESS_TOKEN: ${{ secrets.SUPABASE_ACCESS_TOKEN }} - TF_VAR_linked_project: xguihxuzqibwxjnimxev - TF_VAR_git_branch: ${{ github.head_ref }} - TF_CLI_ARGS_apply: -target=supabase_settings.preview - defaults: - run: - working-directory: supabase/remotes - outputs: - db_user: ${{ steps.branch.outputs.user }} - db_pass: ${{ steps.branch.outputs.password }} - db_host: ${{ steps.branch.outputs.host }} - db_port: ${{ steps.branch.outputs.port }} - jwt_secret: ${{ steps.branch.outputs.jwt_secret }} - ref: ${{ steps.branch.outputs.id }} - status: ${{ steps.branch.outputs.status }} - version: ${{ steps.branch.outputs.version }} - - steps: - - uses: actions/checkout@v4 - - uses: hashicorp/setup-terraform@v3 - with: - terraform_wrapper: false - - - run: terraform init - - run: terraform apply -auto-approve -no-color - - id: branch - run: | - terraform output -json branch_database \ - | jq -r "to_entries|map(\"\(.key)=\(.value|tostring)\")|.[]" \ - >> "$GITHUB_OUTPUT" diff --git a/.github/workflows/config-production.yaml b/.github/workflows/config-production.yaml deleted file mode 100644 index b7f3dd3d63c..00000000000 --- a/.github/workflows/config-production.yaml +++ /dev/null @@ -1,28 +0,0 @@ -name: Config (Production) - -on: - push: - branches: - - master - paths: - - "supabase/remotes/**" - workflow_dispatch: - -jobs: - apply: - runs-on: ubuntu-latest - env: - SUPABASE_ACCESS_TOKEN: ${{ secrets.SUPABASE_ACCESS_TOKEN }} - TF_VAR_linked_project: xguihxuzqibwxjnimxev - defaults: - run: - working-directory: supabase/remotes - - steps: - - uses: actions/checkout@v4 - - uses: hashicorp/setup-terraform@v3 - with: - terraform_wrapper: false - - - run: terraform init - - run: terraform apply -auto-approve -no-color diff --git a/.github/workflows/docs-last-changed.yml b/.github/workflows/docs-last-changed.yml index 71764de9626..94967a403f2 100644 --- a/.github/workflows/docs-last-changed.yml +++ b/.github/workflows/docs-last-changed.yml @@ -26,20 +26,25 @@ jobs: sparse-checkout: | apps/docs + - uses: pnpm/action-setup@v4 + name: Install pnpm + with: + run_install: false + - name: Setup node uses: actions/setup-node@v4 with: node-version-file: '.nvmrc' - name: Download dependencies - run: npm ci + run: pnpm i - name: Update last-changed dates working-directory: ./apps/docs if: ${{ !inputs.reset }} - run: npm run last-changed + run: pnpm run last-changed - name: Reset last-changed dates working-directory: ./apps/docs if: ${{ inputs.reset }} - run: npm run last-changed:reset + run: pnpm run last-changed:reset diff --git a/.github/workflows/docs-lint-v2.yml b/.github/workflows/docs-lint-v2.yml new file mode 100644 index 00000000000..fc1112313fc --- /dev/null +++ b/.github/workflows/docs-lint-v2.yml @@ -0,0 +1,87 @@ +name: '[Docs] Lint v2' +on: + pull_request: + paths: + - '.github/workflows/docs-lint-v2.yml' + - 'supa-mdx-lint.config.toml' + - 'apps/docs/content/**' + push: + branches: + - master + workflow_dispatch: + +env: + CARGO_NET_GIT_FETCH_WITH_CLI: true + +permissions: + contents: write + pull-requests: write + +jobs: + supa-mdx-lint: + name: supa-mdx-lint + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + with: + fetch-depth: 0 + sparse-checkout: | + supa-mdx-lint.config.toml + supa-mdx-lint + apps/docs/content + - name: cache cargo + id: cache-cargo + uses: actions/cache@v4 + with: + path: | + ~/.cargo/bin/ + ~/.cargo/registry/index/ + ~/.cargo/registry/cache/ + ~/.cargo/git/db/ + key: 0be447eba738c3fd56698eff8ad01fa56c2391d7 + - name: install linter + if: steps.cache-cargo.outputs.cache-hit != 'true' + run: cargo install --locked --git https://github.com/supabase-community/supa-mdx-lint --rev 0be447eba738c3fd56698eff8ad01fa56c2391d7 + - name: install reviewdog + uses: reviewdog/action-setup@3f401fe1d58fe77e10d665ab713057375e39b887 # v1.3.0 + with: + reviewdog_version: v0.20.2 + - name: run linter on PR + if: github.event_name == 'pull_request' + env: + BASE_REF: ${{ github.base_ref }} + REVIEWDOG_GITHUB_API_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + set -o pipefail + git diff --name-only origin/$BASE_REF HEAD \ + | { grep -E "^apps/docs/content/" || test $? = 1; } \ + | xargs -r supa-mdx-lint --format rdf \ + | reviewdog -f=rdjsonl -reporter=github-pr-review + - name: run linter on push or workflow dispatch + if: github.event_name == 'push' || github.event_name == 'workflow_dispatch' + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + supa-mdx-lint apps/docs/content || { + echo "Linter failed, attempting to fix errors..." + git config --global user.name 'github-docs-bot' + git config --global user.email 'github-docs-bot@supabase.com' + BRANCH_NAME="bot/docs-lint-fixes" + EXISTING_BRANCH=$(git ls-remote --heads origin $BRANCH_NAME) + if [ -n "$EXISTING_BRANCH" ]; then + git checkout $BRANCH_NAME + else + git checkout -b $BRANCH_NAME + fi + supa-mdx-lint apps/docs/content --fix || FIX_FAILED=1 + git add . + git commit -m '[bot] fix lint errors' || true + git push origin $BRANCH_NAME + if [ -z "$EXISTING_BRANCH" ]; then + gh pr create --title '[bot] fix lint errors' --body 'This PR fixes lint errors in the documentation.' --head $BRANCH_NAME + fi + if [ "${FIX_FAILED:-0}" -eq 1 ]; then + echo "Fix did not correct all errors." + exit 1 + fi + } diff --git a/.github/workflows/docs-mgmt-api-update.yml b/.github/workflows/docs-mgmt-api-update.yml index bfb12b7214b..f395be7acad 100644 --- a/.github/workflows/docs-mgmt-api-update.yml +++ b/.github/workflows/docs-mgmt-api-update.yml @@ -11,31 +11,36 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - with: - ref: master - sparse-checkout: | - apps/docs + - uses: actions/checkout@v4 + with: + ref: master + sparse-checkout: | + apps/docs - - name: Use Node.js - uses: actions/setup-node@v4 - with: - node-version-file: '.nvmrc' - cache: 'npm' + - uses: pnpm/action-setup@v4 + name: Install pnpm + with: + run_install: false - - name: Install deps - run: npm ci + - name: Use Node.js + uses: actions/setup-node@v4 + with: + node-version-file: '.nvmrc' + cache: 'pnpm' - - name: Change to apps/docs/spec directory and run make command - working-directory: apps/docs/spec - run: make download.api.v1 dereference.api.v1 generate.sections.api.v1 format + - name: Install deps + run: pnpm i - - name: Create pull request - uses: peter-evans/create-pull-request@v6 - with: - token: ${{ secrets.GITHUB_TOKEN }} - commit-message: 'feat: update mgmt api docs' - title: 'feat: update mgmt api docs' - body: 'This PR updates mgmt api docs automatically.' - branch: 'gha/auto-update-mgmt-api-docs' - base: 'master' + - name: Change to apps/docs/spec directory and run make command + working-directory: apps/docs/spec + run: make download.api.v1 dereference.api.v1 generate.sections.api.v1 format + + - name: Create pull request + uses: peter-evans/create-pull-request@v6 + with: + token: ${{ secrets.GITHUB_TOKEN }} + commit-message: 'feat: update mgmt api docs' + title: 'feat: update mgmt api docs' + body: 'This PR updates mgmt api docs automatically.' + branch: 'gha/auto-update-mgmt-api-docs' + base: 'master' diff --git a/.github/workflows/docs-sync-troubleshooting.yml b/.github/workflows/docs-sync-troubleshooting.yml new file mode 100644 index 00000000000..f0d2ec07116 --- /dev/null +++ b/.github/workflows/docs-sync-troubleshooting.yml @@ -0,0 +1,56 @@ +name: '[Docs] Sync troubleshooting guides to GitHub Discussions' + +on: + push: + branches: + - master + paths: + - 'apps/docs/content/troubleshooting/**' + workflow_dispatch: + +permissions: + contents: write + pull-requests: write + +jobs: + update-troubleshooting: + runs-on: ubuntu-latest + + env: + SEARCH_GITHUB_APP_ID: ${{ secrets.SEARCH_GITHUB_APP_ID }} + SEARCH_GITHUB_APP_INSTALLATION_ID: ${{ secrets.SEARCH_GITHUB_APP_INSTALLATION_ID }} + SEARCH_GITHUB_APP_PRIVATE_KEY: ${{ secrets.SEARCH_GITHUB_APP_PRIVATE_KEY }} + NEXT_PUBLIC_SUPABASE_URL: ${{ secrets.SEARCH_SUPABASE_URL }} + SUPABASE_SECRET_KEY: ${{ secrets.SEARCH_SUPABASE_SERVICE_ROLE_KEY }} + + steps: + - uses: actions/checkout@v4 + with: + sparse-checkout: | + apps/docs + + - uses: pnpm/action-setup@v4 + name: Install pnpm + with: + run_install: false + + - name: Use Node.js + uses: actions/setup-node@v4 + with: + node-version-file: '.nvmrc' + cache: 'pnpm' + + - name: Install dependencies + run: pnpm i + + - name: Run Troubleshooting script + run: pnpm run -F docs troubleshooting:sync + + - name: Create Pull Request + uses: peter-evans/create-pull-request@5e914681df9dc83aa4e4905692ca88beb2f9e91f # v7.0.5 + with: + commit-message: '[bot] sync troubleshooting guides to db' + title: '[bot] sync troubleshooting guides to db' + author: 'github-docs-sync-bot ' + branch: 'bot/docs-sync-troubleshooting' + branch-suffix: 'random' diff --git a/.github/workflows/docs-tests-smoke.yml b/.github/workflows/docs-tests-smoke.yml index 0ebf7512c19..c5a38916a46 100644 --- a/.github/workflows/docs-tests-smoke.yml +++ b/.github/workflows/docs-tests-smoke.yml @@ -22,14 +22,19 @@ jobs: apps/docs packages + - uses: pnpm/action-setup@v4 + name: Install pnpm + with: + run_install: false + - name: Use Node.js uses: actions/setup-node@v4 with: node-version-file: '.nvmrc' - cache: 'npm' + cache: 'pnpm' - name: Install deps - run: npm ci + run: pnpm i - name: Run tests - run: npm --prefix="apps/docs" run test:smoke + run: pnpm --prefix="apps/docs" run test:smoke diff --git a/.github/workflows/docs-tests.yml b/.github/workflows/docs-tests.yml index 9f89a86c660..f9a36f3cf20 100644 --- a/.github/workflows/docs-tests.yml +++ b/.github/workflows/docs-tests.yml @@ -23,14 +23,19 @@ jobs: examples packages + - uses: pnpm/action-setup@v4 + name: Install pnpm + with: + run_install: false + - name: Use Node.js uses: actions/setup-node@v4 with: node-version-file: '.nvmrc' - cache: 'npm' + cache: 'pnpm' - name: Install deps - run: npm ci + run: pnpm i - name: Run tests - run: npm run test:docs + run: pnpm run test:docs diff --git a/.github/workflows/external-pr-comment.yml b/.github/workflows/external-pr-comment.yml new file mode 100644 index 00000000000..5e3c36dd358 --- /dev/null +++ b/.github/workflows/external-pr-comment.yml @@ -0,0 +1,31 @@ +name: Comment on external PRs + +on: + pull_request_target: + types: [opened] + +permissions: + pull-requests: write + +jobs: + comment-on-external-pr: + runs-on: ubuntu-latest + if: github.event.pull_request.head.repo.full_name != github.repository + steps: + - name: Comment on PR + uses: actions/github-script@v7 + with: + script: | + const commentBody = ` + Thanks for contributing to Supabase! ❤️ Our team will review your PR. + + A few tips for a smoother review process: + - If you have a local version of the repo, run \`pnpm run format\` to make sure formatting checks pass. + - Once we've reviewed your PR, please don't trivially merge master (don't click \`Update branch\` if there are no merge conflicts to be fixed). This invalidates any pre-merge checks we've run. + `; + github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + body: commentBody + }); diff --git a/.github/workflows/lint-prose.yml b/.github/workflows/lint-prose.yml index c2a402697a7..a652acb37c3 100644 --- a/.github/workflows/lint-prose.yml +++ b/.github/workflows/lint-prose.yml @@ -11,15 +11,21 @@ jobs: name: runner / vale runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: sparse-checkout: | .vale.ini vale apps/docs/content + - name: Get changed files + id: changed-files + env: + BASE_REF: ${{ github.base_ref }} + SHA: ${{ github.sha }} + run: | + git fetch origin $BASE_REF + echo "CHANGED_FILES=$(git diff --name-only --diff-filter=ACMRT origin/$BASE_REF $SHA | grep '^apps/docs/content/' | tr '\n' ',' | sed 's/,$//')" >> $GITHUB_OUTPUT - uses: errata-ai/vale-action@reviewdog with: - files: apps/docs/content - # [Charis] Temporary because we still have to go through giant backlog of existing errors - # Don't want people to worry about this (just want them looking at the specific PR comments) - # fail_on_error: true + files: '${{ steps.changed-files.outputs.CHANGED_FILES }}' + separator: ',' diff --git a/.github/workflows/prettier.yml b/.github/workflows/prettier.yml index 9d7053db16b..9381cd0e98e 100644 --- a/.github/workflows/prettier.yml +++ b/.github/workflows/prettier.yml @@ -18,17 +18,21 @@ jobs: uses: actions/checkout@v4 with: sparse-checkout: apps + - uses: pnpm/action-setup@v4 + name: Install pnpm + with: + run_install: false - name: Setup node uses: actions/setup-node@v4 with: node-version-file: '.nvmrc' - # Installing all dependencies takes up to three minutes, hacking around to only installing prettier+deps + cache: 'pnpm' - name: Download dependencies run: | - npm ci + pnpm i - name: Run prettier run: |- - npm run test:prettier + pnpm run test:prettier # i18n is not a node package, so we handle that one separately format-i18n: @@ -39,19 +43,21 @@ jobs: with: sparse-checkout: | i18n + - uses: pnpm/action-setup@v4 + name: Install pnpm + with: + run_install: false - name: Setup node uses: actions/setup-node@v4 with: node-version-file: '.nvmrc' - # Installing all dependencies takes up to three minutes, hacking around to only installing prettier+deps + cache: 'pnpm' - name: Download dependencies run: | - rm package.json - rm package-lock.json - npm i prettier@4.0.0-alpha.8 prettier-plugin-sql-cst + pnpm i - name: Run prettier run: |- - npx prettier -c 'i18n/**/*.{js,jsx,ts,tsx,css,md,mdx,json}' + pnpm exec prettier -c 'i18n/**/*.{js,jsx,ts,tsx,css,md,mdx,json}' format-sql: runs-on: ubuntu-latest @@ -62,13 +68,19 @@ jobs: sparse-checkout: | apps/docs/pages apps/docs/content - # Installing all dependencies takes up to three minutes, hacking around to only installing prettier+deps + - uses: pnpm/action-setup@v4 + name: Install pnpm + with: + run_install: false + - name: Setup node + uses: actions/setup-node@v4 + with: + node-version-file: '.nvmrc' + cache: 'pnpm' - name: Download dependencies run: | - rm package.json - rm package-lock.json - npm i prettier@4.0.0-alpha.8 prettier-plugin-sql-cst + pnpm i - name: Run prettier run: |- # Check mdx files which contain sql code blocks - grep -lr '```sql' apps/docs/{pages,content}/**/*.mdx | xargs npx prettier -c + grep -lr '```sql' apps/docs/{pages,content}/**/*.mdx | xargs pnpm exec prettier -c diff --git a/.github/workflows/search.yml b/.github/workflows/search.yml index 3986992f2f6..60385047ea4 100644 --- a/.github/workflows/search.yml +++ b/.github/workflows/search.yml @@ -38,13 +38,18 @@ jobs: apps/www/.env.local.example supabase + - uses: pnpm/action-setup@v4 + name: Install pnpm + with: + run_install: false + - name: Setup node uses: actions/setup-node@v4 with: node-version-file: '.nvmrc' - name: Download dependencies - run: npm ci + run: pnpm i # Need the miscellaneous use API, which is available publicly (by design) in www - name: Copy environment variables @@ -53,9 +58,9 @@ jobs: - name: Update embeddings working-directory: ./apps/docs if: ${{ !inputs.refresh }} - run: npm run embeddings + run: pnpm run embeddings - name: Refresh embeddings working-directory: ./apps/docs if: ${{ inputs.refresh }} - run: npm run embeddings:refresh + run: pnpm run embeddings:refresh diff --git a/.github/workflows/self-host-tests-smoke.yml b/.github/workflows/self-host-tests-smoke.yml new file mode 100644 index 00000000000..dd3d1b76a0c --- /dev/null +++ b/.github/workflows/self-host-tests-smoke.yml @@ -0,0 +1,25 @@ +name: Self-Host Tests Smoke + +on: + pull_request: + branches: ['master'] + paths: + - 'docker/**/*' + +# Cancel old builds on new commit for same workflow + branch/PR +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + with: + sparse-checkout: | + docker/ + - name: Run docker-compose up + # Ensure all services can be started and healthy with default config + run: cd docker && cp .env.example .env && docker compose up --wait diff --git a/.github/workflows/playwright.yml b/.github/workflows/studio-e2e-tests.yml similarity index 66% rename from .github/workflows/playwright.yml rename to .github/workflows/studio-e2e-tests.yml index 2508ef13315..163c42dbe01 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/studio-e2e-tests.yml @@ -1,15 +1,17 @@ -name: Playwright Tests +name: Studio E2E Tests on: push: branches: [master] paths: - 'apps/studio/**' - - 'package-lock.json' + - 'tests/studio-tests/**' + - 'pnpm-lock.yaml' pull_request: branches: [master] paths: - 'apps/studio/**' - - 'package-lock.json' + - 'tests/studio-tests/**' + - 'pnpm-lock.yaml' # Cancel old builds on new commit for same workflow + branch/PR concurrency: @@ -23,23 +25,29 @@ jobs: steps: - uses: actions/checkout@v4 + - uses: pnpm/action-setup@v4 + name: Install pnpm + with: + run_install: false - name: Use Node.js uses: actions/setup-node@v4 with: node-version-file: '.nvmrc' - cache: 'npm' + cache: 'pnpm' - uses: supabase/setup-cli@v1 + with: + version: latest - name: Install dependencies - run: npm ci + run: pnpm i - name: Install Playwright Browsers - run: npx playwright install --with-deps + run: pnpm -C tests/studio-tests exec playwright install --with-deps - name: Run Playwright tests - run: npm run test:playwright + run: pnpm test:e2e:studio-local # mark the action as succeeded even if the tests failed. This is temporarily until we make the tests more stable. # continue-on-error: true - uses: actions/upload-artifact@v4 if: always() with: name: playwright-report - path: playwright-tests/playwright-report/ + path: tests/studio-tests/playwright-report/ retention-days: 7 diff --git a/.github/workflows/studio-tests.yml b/.github/workflows/studio-unit-tests.yml similarity index 84% rename from .github/workflows/studio-tests.yml rename to .github/workflows/studio-unit-tests.yml index 1dbc0bd8dd6..0f41b9e49cf 100644 --- a/.github/workflows/studio-tests.yml +++ b/.github/workflows/studio-unit-tests.yml @@ -8,12 +8,12 @@ on: branches: [master, studio] paths: - 'apps/studio/**' - - 'package-lock.json' + - 'pnpm-lock.yaml' pull_request: branches: [master, studio] paths: - 'apps/studio/**' - - 'package-lock.json' + - 'pnpm-lock.yaml' # Cancel old builds on new commit for same workflow + branch/PR concurrency: @@ -31,17 +31,21 @@ jobs: sparse-checkout: | apps/studio packages + - uses: pnpm/action-setup@v4 + name: Install pnpm + with: + run_install: false - name: Use Node.js uses: actions/setup-node@v4 with: node-version-file: '.nvmrc' - cache: 'npm' + cache: 'pnpm' - name: Install deps - run: npm ci + run: pnpm i working-directory: ./ - name: Run Tests env: # Default is 2 GB, increase to have less frequent OOM errors NODE_OPTIONS: '--max_old_space_size=3072' - run: npm run test:studio + run: pnpm run test:studio working-directory: ./ diff --git a/.github/workflows/typecheck.yml b/.github/workflows/typecheck.yml index 70481bb7bb3..76e474371a1 100644 --- a/.github/workflows/typecheck.yml +++ b/.github/workflows/typecheck.yml @@ -19,17 +19,22 @@ jobs: - name: Checkout uses: actions/checkout@v4 + - uses: pnpm/action-setup@v4 + name: Install pnpm + with: + run_install: false + - name: Use Node.js uses: actions/setup-node@v4 with: node-version-file: '.nvmrc' - cache: 'npm' + cache: 'pnpm' - name: Install deps - run: npm ci + run: pnpm i - name: Run TypeScript type check - run: npx turbo run typecheck + run: pnpm exec turbo run typecheck - name: Run Lint - run: npx turbo run lint + run: pnpm exec turbo run lint diff --git a/.github/workflows/ui-patterns-tests.yml b/.github/workflows/ui-patterns-tests.yml index a2fe4dc775f..1f5b93696d8 100644 --- a/.github/workflows/ui-patterns-tests.yml +++ b/.github/workflows/ui-patterns-tests.yml @@ -21,14 +21,19 @@ jobs: sparse-checkout: | packages + - uses: pnpm/action-setup@v4 + name: Install pnpm + with: + run_install: false + - name: Use Node.js uses: actions/setup-node@v4 with: node-version-file: '.nvmrc' - cache: 'npm' + cache: 'pnpm' - name: Install deps - run: npm ci + run: pnpm i - name: Run tests - run: npm run test:ui-patterns + run: pnpm run test:ui-patterns diff --git a/.github/workflows/ui-tests.yml b/.github/workflows/ui-tests.yml index 126edd5b9a5..80b75b974cd 100644 --- a/.github/workflows/ui-tests.yml +++ b/.github/workflows/ui-tests.yml @@ -21,14 +21,19 @@ jobs: sparse-checkout: | packages + - uses: pnpm/action-setup@v4 + name: Install pnpm + with: + run_install: false + - name: Use Node.js uses: actions/setup-node@v4 with: node-version-file: '.nvmrc' - cache: 'npm' + cache: 'pnpm' - name: Install deps - run: npm ci + run: pnpm i - name: Run tests - run: npm run test:ui + run: pnpm run test:ui diff --git a/.npmrc b/.npmrc index 4fd021952d5..a4047535344 100644 --- a/.npmrc +++ b/.npmrc @@ -1 +1,2 @@ -engine-strict=true \ No newline at end of file +engine-strict=true +update-notifier=false diff --git a/.prettierignore b/.prettierignore index 98bd8c16382..4c3f5a35591 100644 --- a/.prettierignore +++ b/.prettierignore @@ -7,12 +7,14 @@ apps/**/out # prettier-plugin-sql-cst only supports sqlite syntax **/supabase/migrations/*.sql apps/www/schema.sql +apps/www/public/images/* apps/docs/**/generated/* +apps/docs/examples examples/slack-clone/nextjs-slack-clone/full-schema.sql # ignore files with custom js formatting -apps/docs/pages/guides/auth/*.mdx -apps/docs/pages/guides/integrations/*.mdx apps/studio/public apps/**/.turbo apps/docs/CONTRIBUTING.md -apps/design-system/__registry__ \ No newline at end of file +apps/design-system/__registry__ +packages/icons/__registry__ +packages/icons/src/icons/*.ts diff --git a/.prettierrc b/.prettierrc index 5609026b6f4..58dc1645bf8 100644 --- a/.prettierrc +++ b/.prettierrc @@ -6,7 +6,6 @@ "printWidth": 100, "endOfLine": "lf", "sqlKeywordCase": "lower", - "pluginSearchDirs": false, "plugins": ["prettier-plugin-sql-cst"], "overrides": [ { diff --git a/DEVELOPERS.md b/DEVELOPERS.md index 6aab339f865..26f40136c82 100644 --- a/DEVELOPERS.md +++ b/DEVELOPERS.md @@ -38,7 +38,8 @@ You will need to install and configure the following dependencies on your machin - [Git](http://git-scm.com/) - [Node.js v20.x (LTS)](http://nodejs.org) -- [npm](https://www.npmjs.com/) version 10.x.x or higher +- [pnpm](https://pnpm.io/) version 9.x.x or higher +- [make](https://www.gnu.org/software/make/) or the equivalent to `build-essentials` for your OS - [Docker](https://docs.docker.com/get-docker/) (to run studio locally) ## Local development @@ -69,7 +70,7 @@ To contribute code to [Supabase](https://supabase.com), you must fork the [Supab 1. Install the dependencies in the root of the repo. ```sh - npm install # install dependencies + pnpm install # install dependencies ``` 2. Copy the example `.env.local.example` to `.env.local` @@ -80,7 +81,7 @@ To contribute code to [Supabase](https://supabase.com), you must fork the [Supab 3. After that you can run the apps simultaneously with the following. ```sh - npm run dev # start all the applications + pnpm dev # start all the applications ``` Then visit, and edit, any of the following sites: @@ -96,7 +97,7 @@ Then visit, and edit, any of the following sites: You can run any of the sites individually by using the scope name. For example: ```sh -npm run dev:www +pnpm dev:www ``` Note: Particularly for `www` make sure you have copied `apps/www/.env.local.example` to `apps/www/.env.local` @@ -114,17 +115,13 @@ The monorepo has a set of shared components under `/packages`: #### Installing packages -Installing a package with NPM workspaces requires you to add the `-w` flag to tell NPM which workspace you want to install into. Do not install dependencies in their local folder, install them from the route using the `-w` flag. - -The format is: `npm install -w=`. +Installing a package in a specific workspace requires you to move to the workspace and then run the install command. For example: -- `npm install react -w common`: installs into `./packages/common` -- `npm install react -w www`: installs into `./apps/www` -- `npm install react -w studio`: installs into `./apps/studio` +1. `cd apps/studio`: move to the `studio` workspace +2. `pnpm add react`: installs `react` into `studio` workspace -You do not need to install `devDependencies` in each workspace. These can all be installed in the root package. --- diff --git a/README.md b/README.md index 437878278b6..3a165a26d6f 100644 --- a/README.md +++ b/README.md @@ -52,10 +52,10 @@ You can also [self-host](https://supabase.com/docs/guides/hosting/overview) and - [Postgres](https://www.postgresql.org/) is an object-relational database system with over 30 years of active development that has earned it a strong reputation for reliability, feature robustness, and performance. - [Realtime](https://github.com/supabase/realtime) is an Elixir server that allows you to listen to PostgreSQL inserts, updates, and deletes using websockets. Realtime polls Postgres' built-in replication functionality for database changes, converts changes to JSON, then broadcasts the JSON over websockets to authorized clients. -- [PostgREST](http://postgrest.org/) is a web server that turns your PostgreSQL database directly into a RESTful API +- [PostgREST](http://postgrest.org/) is a web server that turns your PostgreSQL database directly into a RESTful API. - [GoTrue](https://github.com/supabase/gotrue) is a JWT based API for managing users and issuing JWT tokens. - [Storage](https://github.com/supabase/storage-api) provides a RESTful interface for managing Files stored in S3, using Postgres to manage permissions. -- [pg_graphql](http://github.com/supabase/pg_graphql/) a PostgreSQL extension that exposes a GraphQL API +- [pg_graphql](http://github.com/supabase/pg_graphql/) a PostgreSQL extension that exposes a GraphQL API. - [postgres-meta](https://github.com/supabase/postgres-meta) is a RESTful API for managing your Postgres, allowing you to fetch tables, add roles, and run queries, etc. - [Kong](https://github.com/Kong/kong) is a cloud-native API gateway. diff --git a/apps/design-system/__registry__/index.tsx b/apps/design-system/__registry__/index.tsx index 57cf860d20b..9ecdd2dd9cc 100644 --- a/apps/design-system/__registry__/index.tsx +++ b/apps/design-system/__registry__/index.tsx @@ -654,6 +654,17 @@ export const Index: Record = { subcategory: "undefined", chunks: [] }, + "dialog-centered-off": { + name: "dialog-centered-off", + type: "components:example", + registryDependencies: ["dialog","button"], + component: React.lazy(() => import("@/registry/default/example/dialog-centered-off")), + source: "", + files: ["registry/default/example/dialog-centered-off.tsx"], + category: "undefined", + subcategory: "undefined", + chunks: [] + }, "drawer-demo": { name: "drawer-demo", type: "components:example", @@ -1787,6 +1798,72 @@ export const Index: Record = { subcategory: "undefined", chunks: [] }, + "inner-side-menu-demo": { + name: "inner-side-menu-demo", + type: "components:example", + registryDependencies: undefined, + component: React.lazy(() => import("@/registry/default/example/inner-side-menu-demo")), + source: "", + files: ["registry/default/example/inner-side-menu-demo.tsx"], + category: "undefined", + subcategory: "undefined", + chunks: [] + }, + "inner-side-menu-static-titles": { + name: "inner-side-menu-static-titles", + type: "components:example", + registryDependencies: undefined, + component: React.lazy(() => import("@/registry/default/example/inner-side-menu-static-titles")), + source: "", + files: ["registry/default/example/inner-side-menu-static-titles.tsx"], + category: "undefined", + subcategory: "undefined", + chunks: [] + }, + "inner-side-menu-loading": { + name: "inner-side-menu-loading", + type: "components:example", + registryDependencies: undefined, + component: React.lazy(() => import("@/registry/default/example/inner-side-menu-loading")), + source: "", + files: ["registry/default/example/inner-side-menu-loading.tsx"], + category: "undefined", + subcategory: "undefined", + chunks: [] + }, + "inner-side-menu-multiple-sections": { + name: "inner-side-menu-multiple-sections", + type: "components:example", + registryDependencies: undefined, + component: React.lazy(() => import("@/registry/default/example/inner-side-menu-multiple-sections")), + source: "", + files: ["registry/default/example/inner-side-menu-multiple-sections.tsx"], + category: "undefined", + subcategory: "undefined", + chunks: [] + }, + "inner-side-menu-empty": { + name: "inner-side-menu-empty", + type: "components:example", + registryDependencies: undefined, + component: React.lazy(() => import("@/registry/default/example/inner-side-menu-empty")), + source: "", + files: ["registry/default/example/inner-side-menu-empty.tsx"], + category: "undefined", + subcategory: "undefined", + chunks: [] + }, + "inner-side-menu-with-search": { + name: "inner-side-menu-with-search", + type: "components:example", + registryDependencies: undefined, + component: React.lazy(() => import("@/registry/default/example/inner-side-menu-with-search")), + source: "", + files: ["registry/default/example/inner-side-menu-with-search.tsx"], + category: "undefined", + subcategory: "undefined", + chunks: [] + }, "multi-select-demo": { name: "multi-select-demo", type: "components:example", @@ -1798,13 +1875,79 @@ export const Index: Record = { subcategory: "undefined", chunks: [] }, - "multi-select-form": { - name: "multi-select-form", + "multi-select-disabled": { + name: "multi-select-disabled", type: "components:example", registryDependencies: undefined, - component: React.lazy(() => import("@/registry/default/example/multi-select-form")), + component: React.lazy(() => import("@/registry/default/example/multi-select-disabled")), source: "", - files: ["registry/default/example/multi-select-form.tsx"], + files: ["registry/default/example/multi-select-disabled.tsx"], + category: "undefined", + subcategory: "undefined", + chunks: [] + }, + "multi-select-badge-limit-wrap": { + name: "multi-select-badge-limit-wrap", + type: "components:example", + registryDependencies: undefined, + component: React.lazy(() => import("@/registry/default/example/multi-select-badge-limit-wrap")), + source: "", + files: ["registry/default/example/multi-select-badge-limit-wrap.tsx"], + category: "undefined", + subcategory: "undefined", + chunks: [] + }, + "multi-select-badge-limit": { + name: "multi-select-badge-limit", + type: "components:example", + registryDependencies: undefined, + component: React.lazy(() => import("@/registry/default/example/multi-select-badge-limit")), + source: "", + files: ["registry/default/example/multi-select-badge-limit.tsx"], + category: "undefined", + subcategory: "undefined", + chunks: [] + }, + "multi-select-combobox": { + name: "multi-select-combobox", + type: "components:example", + registryDependencies: undefined, + component: React.lazy(() => import("@/registry/default/example/multi-select-combobox")), + source: "", + files: ["registry/default/example/multi-select-combobox.tsx"], + category: "undefined", + subcategory: "undefined", + chunks: [] + }, + "multi-select-combobox-creatable": { + name: "multi-select-combobox-creatable", + type: "components:example", + registryDependencies: undefined, + component: React.lazy(() => import("@/registry/default/example/multi-select-combobox-creatable")), + source: "", + files: ["registry/default/example/multi-select-combobox-creatable.tsx"], + category: "undefined", + subcategory: "undefined", + chunks: [] + }, + "multi-select-deletable-badge": { + name: "multi-select-deletable-badge", + type: "components:example", + registryDependencies: undefined, + component: React.lazy(() => import("@/registry/default/example/multi-select-deletable-badge")), + source: "", + files: ["registry/default/example/multi-select-deletable-badge.tsx"], + category: "undefined", + subcategory: "undefined", + chunks: [] + }, + "multi-select-inline-search-input": { + name: "multi-select-inline-search-input", + type: "components:example", + registryDependencies: undefined, + component: React.lazy(() => import("@/registry/default/example/multi-select-inline-search-input")), + source: "", + files: ["registry/default/example/multi-select-inline-search-input.tsx"], category: "undefined", subcategory: "undefined", chunks: [] @@ -2029,6 +2172,17 @@ export const Index: Record = { subcategory: "undefined", chunks: [] }, + "logs-bar-chart": { + name: "logs-bar-chart", + type: "components:example", + registryDependencies: undefined, + component: React.lazy(() => import("@/registry/default/example/logs-bar-chart")), + source: "", + files: ["registry/default/example/logs-bar-chart.tsx"], + category: "undefined", + subcategory: "undefined", + chunks: [] + }, "chart-area-axes": { name: "chart-area-axes", type: "components:block", diff --git a/apps/design-system/app/Providers.tsx b/apps/design-system/app/Providers.tsx index be3bdf78611..87bac1f1e2f 100644 --- a/apps/design-system/app/Providers.tsx +++ b/apps/design-system/app/Providers.tsx @@ -1,17 +1,16 @@ 'use client' -import * as React from 'react' import { Provider as JotaiProvider } from 'jotai' import { ThemeProvider as NextThemesProvider } from 'next-themes' import { ThemeProviderProps } from 'next-themes/dist/types' -import { TooltipProvider_Shadcn_ } from 'ui' +import { TooltipProvider } from 'ui' export function ThemeProvider({ children, ...props }: ThemeProviderProps) { return ( - {children} + {children} ) diff --git a/apps/design-system/config/docs.ts b/apps/design-system/config/docs.ts index ad2619adf12..e96ec0b2e5c 100644 --- a/apps/design-system/config/docs.ts +++ b/apps/design-system/config/docs.ts @@ -102,6 +102,11 @@ export const docsConfig: DocsConfig = { href: '/docs/fragments/info-tooltip', items: [], }, + { + title: 'Inner Side Menu', + href: '/docs/fragments/inner-side-menu', + items: [], + }, { title: 'Form Item Layout', href: '/docs/fragments/form-item-layout', @@ -112,6 +117,11 @@ export const docsConfig: DocsConfig = { href: '/docs/fragments/multi-select', items: [], }, + { + title: 'Logs Bar Chart', + href: '/docs/fragments/logs-bar-chart', + items: [], + }, ], }, { @@ -339,6 +349,11 @@ export const docsConfig: DocsConfig = { href: '/docs/components/slider', items: [], }, + { + title: 'Sidebar', + href: '/docs/components/sidebar', + items: [], + }, { title: 'Sonner', href: '/docs/components/sonner', diff --git a/apps/design-system/content/docs/changelog.mdx b/apps/design-system/content/docs/changelog.mdx index cbee68f1521..1ae4506b73a 100644 --- a/apps/design-system/content/docs/changelog.mdx +++ b/apps/design-system/content/docs/changelog.mdx @@ -4,6 +4,14 @@ description: Latest updates and announcements. toc: false --- +## 18th Oct 2024 - Update multi-select + +Improved Multi Select with combobox, badge limit, and more examples. + +- [Multi Select](/design-system/components/multi-select) + +[PR](https://github.com/supabase/supabase/pull/29695) + ## 12th June 2024 - Button styles updated - [` +} +``` + +## SidebarRail + +The `SidebarRail` component is used to render a rail within a `Sidebar`. This rail can be used to toggle the sidebar. + +```tsx showLineNumbers + + + + + + + + +``` + +## Data Fetching + +### React Server Components + +Here's an example of a `SidebarMenu` component rendering a list of projects using React Server Components. + +
+ +
+ A sidebar menu using React Server Components. +
+
+ +```tsx showLineNumbers {6} title="Skeleton to show loading state." +function NavProjectsSkeleton() { + return ( + + {Array.from({ length: 5 }).map((_, index) => ( + + + + ))} + + ) +} +``` + +```tsx showLineNumbers {2} title="Server component fetching data." +async function NavProjects() { + const projects = await fetchProjects() + + return ( + + {projects.map((project) => ( + + + + + {project.name} + + + + ))} + + ) +} +``` + +```tsx showLineNumbers {8-10} title="Usage with React Suspense." +function AppSidebar() { + return ( + + + + Projects + + }> + + + + + + + ) +} +``` + +### SWR and React Query + +You can use the same approach with [SWR](https://swr.vercel.app/) or [react-query](https://tanstack.com/query/latest/docs/framework/react/overview). + +```tsx showLineNumbers title="SWR" +function NavProjects() { + const { data, isLoading } = useSWR("/api/projects", fetcher) + + if (isLoading) { + return ( + + {Array.from({ length: 5 }).map((_, index) => ( + + + + ))} + + ) + } + + if (!data) { + return ... + } + + return ( + + {data.map((project) => ( + + + + + {project.name} + + + + ))} + + ) +} +``` + +```tsx showLineNumbers title="React Query" +function NavProjects() { + const { data, isLoading } = useQuery() + + if (isLoading) { + return ( + + {Array.from({ length: 5 }).map((_, index) => ( + + + + ))} + + ) + } + + if (!data) { + return ... + } + + return ( + + {data.map((project) => ( + + + + + {project.name} + + + + ))} + + ) +} +``` + +## Controlled Sidebar + +Use the `open` and `onOpenChange` props to control the sidebar. + +
+ +
A controlled sidebar.
+
+ +```tsx showLineNumbers +export function AppSidebar() { + const [open, setOpen] = React.useState(false) + + return ( + + + + ) +} +``` + +## Theming + +We use the following CSS variables to theme the sidebar. + +```css +@layer base { + :root { + --sidebar-background: 0 0% 98%; + --sidebar-foreground: 240 5.3% 26.1%; + --sidebar-primary: 240 5.9% 10%; + --sidebar-primary-foreground: 0 0% 98%; + --sidebar-accent: 240 4.8% 95.9%; + --sidebar-accent-foreground: 240 5.9% 10%; + --sidebar-border: 220 13% 91%; + --sidebar-ring: 217.2 91.2% 59.8%; + } + + .dark { + --sidebar-background: 240 5.9% 10%; + --sidebar-foreground: 240 4.8% 95.9%; + --sidebar-primary: 0 0% 98%; + --sidebar-primary-foreground: 240 5.9% 10%; + --sidebar-accent: 240 3.7% 15.9%; + --sidebar-accent-foreground: 240 4.8% 95.9%; + --sidebar-border: 240 3.7% 15.9%; + --sidebar-ring: 217.2 91.2% 59.8%; + } +} +``` + +**We intentionally use different variables for the sidebar and the rest of the application** to make it easy to have a sidebar that is styled differently from the rest of the application. Think a sidebar with a darker shade from the main application. + +## Styling + +Here are some tips for styling the sidebar based on different states. + +- **Styling an element based on the sidebar collapsible state.** The following will hide the `SidebarGroup` when the sidebar is in `icon` mode. + +```tsx + + + + + +``` + +- **Styling a menu action based on the menu button active state.** The following will force the menu action to be visible when the menu button is active. + +```tsx + + + + +``` + +You can find more tips on using states for styling in this [Twitter thread](https://x.com/shadcn/status/1842329158879420864). + +## Changelog + +### 2024-10-30 Cookie handling in setOpen + +- [#5593](https://github.com/shadcn-ui/ui/pull/5593) - Improved setOpen callback logic in ``. + +Update the `setOpen` callback in `` as follows: + +```tsx showLineNumbers +const setOpen = React.useCallback( + (value: boolean | ((value: boolean) => boolean)) => { + const openState = typeof value === 'function' ? value(open) : value + if (setOpenProp) { + setOpenProp(openState) + } else { + _setOpen(openState) + } + + // This sets the cookie to keep the sidebar state. + document.cookie = `${SIDEBAR_COOKIE_NAME}=${openState}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}` + }, + [setOpenProp, open] +) +``` + +### 2024-10-21 Fixed `text-sidebar-foreground` + +- [#5491](https://github.com/shadcn-ui/ui/pull/5491) - Moved `text-sidebar-foreground` from `` to `` component. + +### 2024-10-20 Typo in `useSidebar` hook. + +Fixed typo in `useSidebar` hook. + +```diff showLineNumbers title="sidebar.tsx" +- throw new Error("useSidebar must be used within a Sidebar.") ++ throw new Error("useSidebar must be used within a SidebarProvider.") +``` diff --git a/apps/design-system/content/docs/fragments/inner-side-menu.mdx b/apps/design-system/content/docs/fragments/inner-side-menu.mdx new file mode 100644 index 00000000000..329252c1efe --- /dev/null +++ b/apps/design-system/content/docs/fragments/inner-side-menu.mdx @@ -0,0 +1,142 @@ +--- +title: Inner Side Menu +description: InnerSideMenu is a component that provides a collapsible side menu with multiple sections. +component: true +--- + + + +## Examples + +{/* ### Multiple sections */} + +{/* */} + +### Collapsible menus + +Inner Side Menu uses the `` component to wrap the menu items. +This is a wrapper component around the `` component. + +```jsx {10-12,16-17} +import { + InnerSideMenuCollapsible, + InnerSideMenuCollapsibleTrigger, + InnerSideMenuCollapsibleContent, + InnerSideMenuItem, +} from 'ui-patterns/InnerSideMenu' + +function app() { + return ( + + + + Dashboard + Team + Settings + + + ) +} +``` + +### Static titles + +If you don't need the Collapsible features, you can instead use ``. +This doesn't require the use of the `` parent component wrapped around it. + +You can wrap the `` conponents however you like. Below we've put a `
` around them. + +```jsx {1} +Projects +
+ Dashboard + Team + Settings +
+``` + + + +### Loading state + +Use `` to show a loading state for menu items. + +This item is a wrapper around the `` component, and also includes some y-padding so the items don't layout shift when shifting to loaded state and to look visually separated in a list. + + + +### With search + +Use the following components to add a search input to the menu. + +```jsx + + setSearchTerm(e.target.value)} + aria-labelledby="Search items" + /> + +``` + +There is also a Filter Dropdown component that can be used to sort the menu items. +This can be used inside ``. + +```jsx {9-16} + + setSearchTerm(e.target.value)} + aria-labelledby="Search items" + > + setSort(value)}> + + Sort Alphabetically + + + Sort Reverse Alphabetically + + + + +``` + + + +### Empty list state + +Use `` to show an empty state. + +Use the `actions` prop to add any actions like a ` + } +/> +``` + +You can also use the prop `illustration` to pass in a custom illustration, it accets any React Node. + +```jsx {4-7} + + ../ + + } +/> +``` + + diff --git a/apps/design-system/content/docs/fragments/logs-bar-chart.mdx b/apps/design-system/content/docs/fragments/logs-bar-chart.mdx new file mode 100644 index 00000000000..7127ecd2eac --- /dev/null +++ b/apps/design-system/content/docs/fragments/logs-bar-chart.mdx @@ -0,0 +1,21 @@ +--- +title: Logs Bar Chart +description: A stacked bar chart that displays logs errors and successes. +fragment: true +--- + + + +## Usage + +This component is used to display the bar chart in logs pages. + +It requires that the `data` prop is an array of objects with the following shape: + +```ts +type LogsBarChartDatum = { + timestamp: string + ok_count: number + error_count: number +} +``` diff --git a/apps/design-system/content/docs/fragments/multi-select.mdx b/apps/design-system/content/docs/fragments/multi-select.mdx index d1566b198ac..1c3d40faf46 100644 --- a/apps/design-system/content/docs/fragments/multi-select.mdx +++ b/apps/design-system/content/docs/fragments/multi-select.mdx @@ -12,31 +12,69 @@ fragment: true ```tsx import { MultiSelector, - MultiSelectorContent, - MultiSelectorInput, - MultiSelectorItem, - MultiSelectorList, MultiSelectorTrigger, + MultiSelectorContent, + MultiSelectorItem, } from 'ui-patterns/multi-select' ``` ```tsx - - - - - - - Item 1 - Item 2 - Item 3 - + + + + Apple + Banana + Grape + Strawberry ``` ## Examples -### Form +### Disabled - + + +### Badge Combo Box + +Use `MultiSelectorInput`to add a search input. + + + +### Badge Combo Box with creatable selector + +creatable: `boolean` +`creatable` prop on the `MultiSelectorList` component can be used to add a new option if no items match + + + +### Badge Limit + +badgeLimit: `number` | `"wrap"`. +`badgeLimit` prop on the `MultiSelectorTrigger` component can be used to limit the number of badges displayed. + + + +### Badge Limit="wrap" + +`badgeLimit` prop can also be "wrap" to wrap the badges to the next line. + + + +### Deletable Badge + +Add the `deletableBadge` prop on the `MultiSelectorTrigger` component to enable badge removal directly from the badge. + + + +### Inline search input + +Use `mode="inline-combobox"` on the `MultiSelectorTrigger` component to add an inline search input. + + diff --git a/apps/design-system/content/docs/tailwind-classes.mdx b/apps/design-system/content/docs/tailwind-classes.mdx index 270d4886e58..a90261449b9 100644 --- a/apps/design-system/content/docs/tailwind-classes.mdx +++ b/apps/design-system/content/docs/tailwind-classes.mdx @@ -186,9 +186,3 @@ text-border-control | `500` | | `DEFAULT` | | `600` | - -## Storybook - -The current colors are also auto documented in Storybook - -[Storybook Colors](https://ui-storybook-pre-release.vercel.app/) diff --git a/apps/design-system/package.json b/apps/design-system/package.json index 156079b3a62..8a5e56ce5a2 100644 --- a/apps/design-system/package.json +++ b/apps/design-system/package.json @@ -4,25 +4,26 @@ "private": true, "type": "module", "scripts": { + "preinstall": "npx only-allow pnpm", "dev": "next dev --port 3003", - "build": "npm run content:build && npm run build:registry && next build", + "build": "pnpm run content:build && pnpm run build:registry && next build", "build:registry": "tsx --tsconfig ./tsconfig.scripts.json ./scripts/build-registry.mts && prettier --log-level silent --write \"registry/**/*.{ts,tsx,mdx}\" --cache", "start": "next start", "lint": "next lint", "content:build": "contentlayer2 build", + "clean": "rimraf node_modules .next .turbo", "typecheck": "contentlayer2 build && tsc --noEmit -p tsconfig.json" }, "dependencies": { "@hookform/resolvers": "^3.1.1", - "@radix-ui/react-alert-dialog": "^1.0.5", "contentlayer2": "0.4.6", - "eslint-config-supabase": "*", - "fs": "^0.0.1-security", + "eslint-config-supabase": "workspace:*", + "icons": "workspace:*", "jotai": "^2.8.0", "lodash.template": "4.5.0", "lucide-react": "*", "markdown-wasm": "^1.2.0", - "next": "^14.2.10", + "next": "catalog:", "next-contentlayer2": "0.4.6", "next-themes": "^0.3.0", "react": "^18.2.0", @@ -33,32 +34,32 @@ "react-wrap-balancer": "^1.1.0", "recharts": "^2.12.7", "rehype-autolink-headings": "^7.1.0", - "rehype-pretty-code": "^0.6.0", + "rehype-pretty-code": "^0.9.0", "rehype-slug": "^6.0.0", "remark": "^14.0.3", "remark-code-import": "^1.2.0", "remark-gfm": "^4.0.0", "sonner": "^1.5.0", "ts-morph": "^22.0.0", - "ui": "*", - "ui-patterns": "*", + "ui": "workspace:*", + "ui-patterns": "workspace:*", "unist-util-visit": "^5.0.0", "zod": "^3.22.4" }, "devDependencies": { "@shikijs/compat": "^1.1.7", "@types/lodash.template": "4.5.0", - "@types/node": "^20", "@types/react": "^18.2.65", "@types/react-dom": "^18.2.22", "autoprefixer": "^10.0.1", - "config": "*", + "config": "workspace:^", "mdast-util-toc": "^6.1.1", "postcss": "^8", "rimraf": "^4.1.3", "shiki": "^1.1.7", "tailwindcss": "^3.3.0", - "tsconfig": "*", + "tsconfig": "workspace:*", + "tsx": "^4.7.0", "typescript": "~5.5.0", "unist-builder": "3.0.0" } diff --git a/apps/design-system/registry/default/example/combobox-dropdown-menu.tsx b/apps/design-system/registry/default/example/combobox-dropdown-menu.tsx index f2e06033516..b1c29126099 100644 --- a/apps/design-system/registry/default/example/combobox-dropdown-menu.tsx +++ b/apps/design-system/registry/default/example/combobox-dropdown-menu.tsx @@ -3,7 +3,7 @@ import * as React from 'react' import { Calendar, MoreHorizontal, Tags, Trash, User } from 'lucide-react' -import { Button, CommandList_Shadcn_ } from 'ui' +import { Button, CommandList_Shadcn_, ScrollArea } from 'ui' import { Command_Shadcn_, CommandEmpty_Shadcn_, @@ -75,23 +75,25 @@ export default function ComboboxDropdownMenu() { - - No label found. - - {labels.map((label) => ( - { - setLabel(value) - setOpen(false) - }} - > - {label} - - ))} - - + + + No label found. + + {labels.map((label) => ( + { + setLabel(value) + setOpen(false) + }} + > + {label} + + ))} + + + diff --git a/apps/design-system/registry/default/example/dialog-centered-off.tsx b/apps/design-system/registry/default/example/dialog-centered-off.tsx new file mode 100644 index 00000000000..54713321a25 --- /dev/null +++ b/apps/design-system/registry/default/example/dialog-centered-off.tsx @@ -0,0 +1,46 @@ +import { Button, DialogSection, DialogSectionSeparator } from 'ui' +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, + DialogTrigger, +} from 'ui' +import { Input_Shadcn_ } from 'ui' +import { Label_Shadcn_ } from 'ui' + +export default function DialogDemo() { + return ( + + + + + + + This dialog is not centered. + This dialog is not centered. + + + +
+ + Name + + +
+
+ + Username + + +
+
+ + + +
+
+ ) +} diff --git a/apps/design-system/registry/default/example/inner-side-menu-demo.tsx b/apps/design-system/registry/default/example/inner-side-menu-demo.tsx new file mode 100644 index 00000000000..f8a12c28e29 --- /dev/null +++ b/apps/design-system/registry/default/example/inner-side-menu-demo.tsx @@ -0,0 +1,45 @@ +import React from 'react' +import { Card } from 'ui' +import { + InnerSideMenuCollapsible, + InnerSideMenuCollapsibleTrigger, + InnerSideMenuCollapsibleContent, + InnerSideMenuItem, + InnerSideMenuSeparator, +} from 'ui-patterns/InnerSideMenu' + +export default function InnerSideMenuDemo() { + return ( + + + Hello + + Dashboard + Team + Settings + + + + + Hello + + + Dashboard + + Team + Settings + + + + Hello + + + Dashboard + + Team + Settings + + + + ) +} diff --git a/apps/design-system/registry/default/example/inner-side-menu-empty.tsx b/apps/design-system/registry/default/example/inner-side-menu-empty.tsx new file mode 100644 index 00000000000..b9abf8bf2d1 --- /dev/null +++ b/apps/design-system/registry/default/example/inner-side-menu-empty.tsx @@ -0,0 +1,74 @@ +import React from 'react' +import { + InnerSideMenuCollapsible, + InnerSideMenuCollapsibleTrigger, + InnerSideMenuCollapsibleContent, + InnerSideBarShimmeringLoaders, + InnerSideBarEmptyPanel, + InnerSideMenuItem, + InnerSideMenuSeparator, +} from 'ui-patterns/InnerSideMenu' +import { Button, Card } from 'ui' +import { Heart, Pointer } from 'lucide-react' + +export default function InnerSideMenuEmpty() { + const [isLoading, setIsLoading] = React.useState(true) + const [hasItems, setHasItems] = React.useState(false) + + React.useEffect(() => { + // Simulate loading + const timer = setTimeout(() => { + setIsLoading(false) + }, 2000) + return () => clearTimeout(timer) + }, []) + + return ( + + + + + 🚀
} + actions={ + + } + /> + +
+ + + + + +
+ +
+ + + } + actions={ + + } + /> +
+
+ + ) +} diff --git a/apps/design-system/registry/default/example/inner-side-menu-loading.tsx b/apps/design-system/registry/default/example/inner-side-menu-loading.tsx new file mode 100644 index 00000000000..46e096f1c09 --- /dev/null +++ b/apps/design-system/registry/default/example/inner-side-menu-loading.tsx @@ -0,0 +1,32 @@ +import { Card } from 'ui' +import { + InnerSideMenuCollapsible, + InnerSideMenuCollapsibleContent, + InnerSideMenuCollapsibleTrigger, + InnerSideMenuItemLoading, + InnerSideMenuSeparator, +} from 'ui-patterns/InnerSideMenu' + +export default function InnerSideMenuLoading() { + return ( + + + + + + + + + + + + + + + + + + + + ) +} diff --git a/apps/design-system/registry/default/example/inner-side-menu-multiple-sections.tsx b/apps/design-system/registry/default/example/inner-side-menu-multiple-sections.tsx new file mode 100644 index 00000000000..c31c62c0b78 --- /dev/null +++ b/apps/design-system/registry/default/example/inner-side-menu-multiple-sections.tsx @@ -0,0 +1,32 @@ +import React from 'react' +import { + InnerSideMenuCollapsible, + InnerSideMenuCollapsibleTrigger, + InnerSideMenuCollapsibleContent, + InnerSideMenuItem, + InnerSideMenuSeparator, +} from 'ui-patterns/InnerSideMenu' + +export default function InnerSideMenuMultipleSections() { + return ( +
+ + + + + API + + Database + + + + + + + Reports + Usage + + +
+ ) +} diff --git a/apps/design-system/registry/default/example/inner-side-menu-static-titles.tsx b/apps/design-system/registry/default/example/inner-side-menu-static-titles.tsx new file mode 100644 index 00000000000..12df6a102e2 --- /dev/null +++ b/apps/design-system/registry/default/example/inner-side-menu-static-titles.tsx @@ -0,0 +1,40 @@ +import React from 'react' +import { Card } from 'ui' +import { + InnerSideMenuCollapsible, + InnerSideMenuCollapsibleTrigger, + InnerSideMenuCollapsibleContent, + InnerSideMenuItem, + InnerSideMenuSeparator, + InnerSideBarTitle, +} from 'ui-patterns/InnerSideMenu' + +export default function InnerSideMenuBasic() { + return ( + +
+ Projects + Dashboard + Team + Settings +
+ +
+ Projects + + Dashboard + + Team + Settings +
+
+ Projects + + Dashboard + + Team + Settings +
+
+ ) +} diff --git a/apps/design-system/registry/default/example/inner-side-menu-with-search.tsx b/apps/design-system/registry/default/example/inner-side-menu-with-search.tsx new file mode 100644 index 00000000000..1fdbac66c4f --- /dev/null +++ b/apps/design-system/registry/default/example/inner-side-menu-with-search.tsx @@ -0,0 +1,92 @@ +import React, { useState, useMemo } from 'react' +import { Card } from 'ui' +import { + InnerSideMenuCollapsible, + InnerSideMenuCollapsibleTrigger, + InnerSideMenuCollapsibleContent, + InnerSideMenuItem, + InnerSideBarFilters, + InnerSideBarFilterSearchInput, + InnerSideBarFilterSortDropdown, + InnerSideBarFilterSortDropdownItem, + InnerSideBarEmptyPanel, +} from 'ui-patterns/InnerSideMenu' + +export default function InnerSideMenuWithSearch() { + const [sort, setSort] = useState('alphabetical') + const [searchTerm, setSearchTerm] = useState('') + + const foodCategories = { + Fruits: ['Apple', 'Banana', 'Orange', 'Strawberry'], + Vegetables: ['Carrot', 'Broccoli', 'Spinach', 'Tomato'], + Grains: ['Rice', 'Wheat', 'Oats', 'Quinoa'], + Proteins: ['Chicken', 'Beef', 'Fish', 'Tofu'], + } + + const filteredAndSortedCategories = useMemo(() => { + return Object.entries(foodCategories).reduce( + (acc, [category, items]) => { + let filteredItems = items.filter((item) => + item.toLowerCase().includes(searchTerm.toLowerCase()) + ) + + // Sort the filtered items + filteredItems.sort((a, b) => { + if (sort === 'alphabetical') { + return a.localeCompare(b) + } else { + return b.localeCompare(a) + } + }) + + acc[category] = filteredItems + return acc + }, + {} as Record + ) + }, [foodCategories, searchTerm, sort]) + + return ( + + + setSearchTerm(e.target.value)} + aria-labelledby="Search items" + > + setSort(value)}> + + Sort Alphabetically + + + Sort Reverse Alphabetically + + + + + + {Object.entries(filteredAndSortedCategories).map(([category, items]) => ( + + + + {items.length > 0 ? ( + items.map((item) => ( + + {item} + + )) + ) : ( + + )} + + + ))} + + ) +} diff --git a/apps/design-system/registry/default/example/logs-bar-chart.tsx b/apps/design-system/registry/default/example/logs-bar-chart.tsx new file mode 100644 index 00000000000..e639f355f1a --- /dev/null +++ b/apps/design-system/registry/default/example/logs-bar-chart.tsx @@ -0,0 +1,21 @@ +import { LogsBarChart } from 'ui-patterns/LogsBarChart' + +export default function LogsBarChartDemo() { + const data = Array.from({ length: 100 }, (_, i) => { + const date = new Date() + date.setMinutes(date.getMinutes() - i * 5) // Each point 5 minutes apart + + return { + timestamp: date.toISOString(), + ok_count: Math.floor(Math.random() * 100), // Random value 0-99 + error_count: Math.floor(Math.random() * 50), // Random value 0-50 + warning_count: Math.floor(Math.random() * 50), // Random value 0-50 + } + }).reverse() + + return ( +
+ +
+ ) +} diff --git a/apps/design-system/registry/default/example/multi-select-badge-limit-wrap.tsx b/apps/design-system/registry/default/example/multi-select-badge-limit-wrap.tsx new file mode 100644 index 00000000000..a16ac5b47d4 --- /dev/null +++ b/apps/design-system/registry/default/example/multi-select-badge-limit-wrap.tsx @@ -0,0 +1,45 @@ +import { useState } from 'react' +import { + MultiSelector, + MultiSelectorContent, + MultiSelectorItem, + MultiSelectorList, + MultiSelectorTrigger, +} from 'ui-patterns/multi-select' + +export default function MultiSelectDemo() { + const [selectedValues, setSelectedValues] = useState([ + 'Apple', + 'Banana', + 'Cherry', + 'Date', + 'Elderberrie', + ]) + + return ( + + + + + Apple + Banana + Cherry + Date + Elderberrie + Fig + Grape + Kiwi + Mango + Strawberry + + + + ) +} diff --git a/apps/design-system/registry/default/example/multi-select-badge-limit.tsx b/apps/design-system/registry/default/example/multi-select-badge-limit.tsx new file mode 100644 index 00000000000..7081eb253dc --- /dev/null +++ b/apps/design-system/registry/default/example/multi-select-badge-limit.tsx @@ -0,0 +1,48 @@ +import { Minus, Plus } from 'lucide-react' +import { useState } from 'react' +import { Button } from 'ui' +import { + MultiSelector, + MultiSelectorContent, + MultiSelectorItem, + MultiSelectorList, + MultiSelectorTrigger, +} from 'ui-patterns/multi-select' + +export default function MultiSelectDemo() { + const [selectedValues, setSelectedValues] = useState(['Apple', 'Banana', 'Cherry']) + const [limit, setLimit] = useState(2) + + return ( +
+
+ + + Limit: {limit} + + +
+ + + + + Apple + Banana + Cherry + Date + Elderberrie + Fig + Grape + Kiwi + Mango + Strawberry + + + +
+ ) +} diff --git a/apps/design-system/registry/default/example/multi-select-combobox-creatable.tsx b/apps/design-system/registry/default/example/multi-select-combobox-creatable.tsx new file mode 100644 index 00000000000..5b8ddf96c71 --- /dev/null +++ b/apps/design-system/registry/default/example/multi-select-combobox-creatable.tsx @@ -0,0 +1,34 @@ +import { useState } from 'react' +import { + MultiSelector, + MultiSelectorContent, + MultiSelectorInput, + MultiSelectorItem, + MultiSelectorList, + MultiSelectorTrigger, +} from 'ui-patterns/multi-select' + +export default function MultiSelectDemo() { + const [selectedValues, setSelectedValues] = useState([]) + + return ( + + + + + + Mango + Date + Apple + Elderberrie + Fig + Grape + Banana + Kiwi + Strawberry + Cherry + + + + ) +} diff --git a/apps/design-system/registry/default/example/multi-select-combobox.tsx b/apps/design-system/registry/default/example/multi-select-combobox.tsx new file mode 100644 index 00000000000..5a79a6057b1 --- /dev/null +++ b/apps/design-system/registry/default/example/multi-select-combobox.tsx @@ -0,0 +1,34 @@ +import { useState } from 'react' +import { + MultiSelector, + MultiSelectorContent, + MultiSelectorInput, + MultiSelectorItem, + MultiSelectorList, + MultiSelectorTrigger, +} from 'ui-patterns/multi-select' + +export default function MultiSelectDemo() { + const [selectedValues, setSelectedValues] = useState([]) + + return ( + + + + + + Apple + Banana + Cherry + Date + Elderberrie + Fig + Grape + Kiwi + Mango + Strawberry + + + + ) +} diff --git a/apps/design-system/registry/default/example/multi-select-deletable-badge.tsx b/apps/design-system/registry/default/example/multi-select-deletable-badge.tsx new file mode 100644 index 00000000000..daf693d3edf --- /dev/null +++ b/apps/design-system/registry/default/example/multi-select-deletable-badge.tsx @@ -0,0 +1,37 @@ +import { useState } from 'react' +import { + MultiSelector, + MultiSelectorContent, + MultiSelectorItem, + MultiSelectorList, + MultiSelectorTrigger, +} from 'ui-patterns/multi-select' + +export default function MultiSelectDemo() { + const [selectedValues, setSelectedValues] = useState(['Apple', 'Banana']) + + return ( + + + + + Banana + Cherry + Date + Elderberrie + Fig + Grape + Kiwi + Mango + Strawberry + + + + ) +} diff --git a/apps/design-system/registry/default/example/multi-select-demo.tsx b/apps/design-system/registry/default/example/multi-select-demo.tsx index faa684876f7..fafed2a1c4c 100644 --- a/apps/design-system/registry/default/example/multi-select-demo.tsx +++ b/apps/design-system/registry/default/example/multi-select-demo.tsx @@ -2,25 +2,42 @@ import { useState } from 'react' import { MultiSelector, MultiSelectorContent, - MultiSelectorInput, MultiSelectorItem, MultiSelectorList, MultiSelectorTrigger, } from 'ui-patterns/multi-select' export default function MultiSelectDemo() { - const [value, setValue] = useState([]) + const [selectedValues, setSelectedValues] = useState([]) + + const fruits = [ + { value: 'Apple', isDisabled: false }, + { value: 'Banana', isDisabled: false }, + { value: 'Cherry', isDisabled: false }, + { value: 'Date', isDisabled: false }, + { value: 'Elderberrie', isDisabled: false }, + { value: 'Fig', isDisabled: false }, + { value: 'Grape', isDisabled: false }, + { value: 'Kiwi', isDisabled: true }, + { value: 'Mango', isDisabled: false }, + { value: 'Strawberry', isDisabled: false }, + ] return ( - - - - + + - Item 1 - Item 2 - Item 3 + {fruits.map(({ value, isDisabled }) => ( + + {value} + + ))} diff --git a/apps/design-system/registry/default/example/multi-select-disabled.tsx b/apps/design-system/registry/default/example/multi-select-disabled.tsx new file mode 100644 index 00000000000..16a4ba9aad8 --- /dev/null +++ b/apps/design-system/registry/default/example/multi-select-disabled.tsx @@ -0,0 +1,18 @@ +import { useState } from 'react' +import { + MultiSelector, + MultiSelectorContent, + MultiSelectorItem, + MultiSelectorList, + MultiSelectorTrigger, +} from 'ui-patterns/multi-select' + +export default function MultiSelectDemo() { + const [selectedValues, setSelectedValues] = useState([]) + + return ( + + + + ) +} diff --git a/apps/design-system/registry/default/example/multi-select-form.tsx b/apps/design-system/registry/default/example/multi-select-form.tsx deleted file mode 100644 index 83892ee5bab..00000000000 --- a/apps/design-system/registry/default/example/multi-select-form.tsx +++ /dev/null @@ -1,105 +0,0 @@ -'use client' - -import { zodResolver } from '@hookform/resolvers/zod' -import Image from 'next/image' -import { useForm } from 'react-hook-form' -import { toast } from 'sonner' -import z from 'zod' - -import { - Button, - Form_Shadcn_, - FormDescription_Shadcn_, - FormField_Shadcn_, - FormItem_Shadcn_, - FormLabel_Shadcn_, - FormMessage_Shadcn_, -} from 'ui' -import { - MultiSelector, - MultiSelectorContent, - MultiSelectorInput, - MultiSelectorItem, - MultiSelectorList, - MultiSelectorTrigger, -} from 'ui-patterns/multi-select' - -const form = z.object({ - value: z.array(z.string()).nonempty('Please select at least one person'), -}) - -type Form = z.infer - -const users = [ - { - name: 'ThePrimeagen', - picture: 'https://pbs.twimg.com/profile_images/1759330620160049152/2i_wkOoK_400x400.jpg', - }, - { - name: 'Shadcn', - picture: 'https://pbs.twimg.com/profile_images/1593304942210478080/TUYae5z7_400x400.jpg', - }, - { - name: 'Theo', - picture: 'https://pbs.twimg.com/profile_images/1605762947686375425/lsoGWWty_400x400.jpg', - }, -] - -const MultiSelectZod = () => { - const multiForm = useForm
({ - resolver: zodResolver(form), - defaultValues: form.parse({ value: [users[0].name] }), - }) - - const onSubmit = (data: Form) => { - toast('Form submitted ', { description: JSON.stringify(data, null, 2) }) - } - - return ( - - - ( - - Invite people - - - - - - - {users.map((user) => ( - -
- {user.name} - {user.name} -
-
- ))} -
-
-
- - Select people to invite to this event - - -
- )} - /> - - -
- ) -} - -export default MultiSelectZod diff --git a/apps/design-system/registry/default/example/multi-select-inline-search-input.tsx b/apps/design-system/registry/default/example/multi-select-inline-search-input.tsx new file mode 100644 index 00000000000..775762a39f5 --- /dev/null +++ b/apps/design-system/registry/default/example/multi-select-inline-search-input.tsx @@ -0,0 +1,39 @@ +import { useState } from 'react' +import { + MultiSelector, + MultiSelectorContent, + MultiSelectorItem, + MultiSelectorList, + MultiSelectorTrigger, +} from 'ui-patterns/multi-select' + +export default function MultiSelectDemo() { + const [selectedValues, setSelectedValues] = useState(['Apple']) + + return ( + + + + + Apple + Banana + Cherry + Date + Elderberrie + Fig + Grape + Kiwi + Mango + Strawberry + + + + ) +} diff --git a/apps/design-system/registry/default/example/tooltip-demo.tsx b/apps/design-system/registry/default/example/tooltip-demo.tsx index 5584ba3f170..f347278c438 100644 --- a/apps/design-system/registry/default/example/tooltip-demo.tsx +++ b/apps/design-system/registry/default/example/tooltip-demo.tsx @@ -1,22 +1,16 @@ -import { Button } from 'ui' -import { - Tooltip_Shadcn_, - TooltipContent_Shadcn_, - TooltipProvider_Shadcn_, - TooltipTrigger_Shadcn_, -} from 'ui' +import { Button, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from 'ui' export default function TooltipDemo() { return ( - - - + + + - - + +

Add to library

-
-
-
+ + + ) } diff --git a/apps/design-system/registry/default/example/tree-view-multi-select.tsx b/apps/design-system/registry/default/example/tree-view-multi-select.tsx index 2487c8eecd1..669e59da782 100644 --- a/apps/design-system/registry/default/example/tree-view-multi-select.tsx +++ b/apps/design-system/registry/default/example/tree-view-multi-select.tsx @@ -97,7 +97,6 @@ export default function TreeViewDemo() { name={element.name} isEditing={element.metadata?.isEditing === true} onEditSubmit={(value) => { - console.log('value', value) let updatedTreeData = { ...treeData } const findNode = (node: any) => { if (node.id === element.id) { diff --git a/apps/design-system/registry/examples.ts b/apps/design-system/registry/examples.ts index a7e7a595bb7..a9f4de3e7c8 100644 --- a/apps/design-system/registry/examples.ts +++ b/apps/design-system/registry/examples.ts @@ -431,6 +431,12 @@ export const examples: Registry = [ registryDependencies: ['dialog', 'button'], files: ['example/dialog-close-button.tsx'], }, + { + name: 'dialog-centered-off', + type: 'components:example', + registryDependencies: ['dialog', 'button'], + files: ['example/dialog-centered-off.tsx'], + }, { name: 'drawer-demo', type: 'components:example', @@ -1030,15 +1036,75 @@ export const examples: Registry = [ type: 'components:example', files: ['example/info-tooltip-demo.tsx'], }, + { + name: 'inner-side-menu-demo', + type: 'components:example', + files: ['example/inner-side-menu-demo.tsx'], + }, + { + name: 'inner-side-menu-static-titles', + type: 'components:example', + files: ['example/inner-side-menu-static-titles.tsx'], + }, + { + name: 'inner-side-menu-loading', + type: 'components:example', + files: ['example/inner-side-menu-loading.tsx'], + }, + { + name: 'inner-side-menu-multiple-sections', + type: 'components:example', + files: ['example/inner-side-menu-multiple-sections.tsx'], + }, + { + name: 'inner-side-menu-empty', + type: 'components:example', + files: ['example/inner-side-menu-empty.tsx'], + }, + { + name: 'inner-side-menu-with-search', + type: 'components:example', + files: ['example/inner-side-menu-with-search.tsx'], + }, { name: 'multi-select-demo', type: 'components:example', files: ['example/multi-select-demo.tsx'], }, { - name: 'multi-select-form', + name: 'multi-select-disabled', type: 'components:example', - files: ['example/multi-select-form.tsx'], + files: ['example/multi-select-disabled.tsx'], + }, + { + name: 'multi-select-badge-limit-wrap', + type: 'components:example', + files: ['example/multi-select-badge-limit-wrap.tsx'], + }, + { + name: 'multi-select-badge-limit', + type: 'components:example', + files: ['example/multi-select-badge-limit.tsx'], + }, + { + name: 'multi-select-combobox', + type: 'components:example', + files: ['example/multi-select-combobox.tsx'], + }, + { + name: 'multi-select-combobox-creatable', + type: 'components:example', + files: ['example/multi-select-combobox-creatable.tsx'], + }, + { + name: 'multi-select-deletable-badge', + type: 'components:example', + files: ['example/multi-select-deletable-badge.tsx'], + }, + { + name: 'multi-select-inline-search-input', + type: 'components:example', + files: ['example/multi-select-inline-search-input.tsx'], }, { name: 'tree-view-demo', @@ -1140,4 +1206,9 @@ export const examples: Registry = [ type: 'docs:example', files: ['example/expanding-textarea-demo.tsx'], }, + { + name: 'logs-bar-chart', + type: 'components:example', + files: ['example/logs-bar-chart.tsx'], + }, ] diff --git a/apps/design-system/tailwind.config.js b/apps/design-system/tailwind.config.js index 211af90575b..5be9a0d5a1d 100644 --- a/apps/design-system/tailwind.config.js +++ b/apps/design-system/tailwind.config.js @@ -9,6 +9,7 @@ module.exports = config({ // './../../packages/ui/src/**/*.{tsx,ts,js}', './../../packages/ui-patterns/**/*.{tsx,ts,js}', + '!./../../packages/ui-patterns/node_modules/**/*', // ignore node_modules rule ], theme: { extend: { diff --git a/apps/docs/.env.development b/apps/docs/.env.development new file mode 100644 index 00000000000..7c41f0565e4 --- /dev/null +++ b/apps/docs/.env.development @@ -0,0 +1,6 @@ +# IMPORTANT: This file is checked into version control. +# Only publicly available environment variables should be listed here. +# Do not add secrets here. Use .env.local instead. + +NEXT_PUBLIC_MISC_URL="https://obuldanrptloktxcffvn.supabase.co" +NEXT_PUBLIC_MISC_ANON_KEY="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Im9idWxkYW5ycHRsb2t0eGNmZnZuIiwicm9sZSI6ImFub24iLCJpYXQiOjE3MTg2MTQ2ODUsImV4cCI6MjAzNDE5MDY4NX0.NFt49g6DFkc1X5khCzN5p01iAVo2TMxlx88cY1V0E2M" diff --git a/apps/docs/.gitignore b/apps/docs/.gitignore index 2c0147b651a..d3ed2ff3c4d 100644 --- a/apps/docs/.gitignore +++ b/apps/docs/.gitignore @@ -26,3 +26,6 @@ yarn-error.log* # Sitemap public/sitemap.xml + +# Copied examples folder +/examples/ diff --git a/apps/docs/CONTRIBUTING.md b/apps/docs/CONTRIBUTING.md index fa482554221..843af5ed2e4 100644 --- a/apps/docs/CONTRIBUTING.md +++ b/apps/docs/CONTRIBUTING.md @@ -77,7 +77,7 @@ You can usually identify a federated or reference doc because it uses a Next.js Example spec file import: ```js -import specFile from '~/spec/transforms/analytics_v0_openapi_deparsed.json' assert { type: 'json' } +import specFile from '~/spec/transforms/analytics_v0_openapi_deparsed.json' with { type: 'json' } ``` Example repo definition: diff --git a/apps/docs/DEVELOPERS.md b/apps/docs/DEVELOPERS.md index ed8fa91080d..2d9f7cb3684 100644 --- a/apps/docs/DEVELOPERS.md +++ b/apps/docs/DEVELOPERS.md @@ -14,7 +14,7 @@ For a complete run-down on how all of our tools work together, see the main DEVE ## Local setup -[supabase.com/docs](https://supabase.com/docs) is a Next.JS site. You can get setup by following the same steps for all of our other Next.JS projects: +[supabase.com/docs](https://supabase.com/docs) is a Next.js site. You can get setup by following the same steps for all of our other Next.js projects: 1. Follow the steps outlined in the Local Development section of the main [DEVELOPERS.md](https://github.com/supabase/supabase/blob/master/DEVELOPERS.md) 2. If you work at Supabase, run `dev:secrets:pull` to pull down the internal environment variables. If you're a community member, create a `.env` file and add this line to it: `NEXT_PUBLIC_IS_PLATFORM=false` diff --git a/apps/docs/README.md b/apps/docs/README.md index c8a0cd91609..7803882d6c7 100644 --- a/apps/docs/README.md +++ b/apps/docs/README.md @@ -31,7 +31,7 @@ We use documentation specifications which can be used to generate human-readable - CLISpec (custom to Supabase): for CLI commands and usage. The benefit of using custom specifications is that we can generate many other types from a strict schema (eg, HTML and manpages). -It also means that we can switch to any documentation system we want. On this site we use Next.JS, but on Supabase's official website, we use a custom React site and expose only a subset of the available API for each tool. +It also means that we can switch to any documentation system we want. On this site we use Next.js, but on Supabase's official website, we use a custom React site and expose only a subset of the available API for each tool. ## Contributing diff --git a/apps/docs/app/api/ai/docs/route.ts b/apps/docs/app/api/ai/docs/route.ts index 3b1df3ee2a7..b88d7837f82 100644 --- a/apps/docs/app/api/ai/docs/route.ts +++ b/apps/docs/app/api/ai/docs/route.ts @@ -6,7 +6,7 @@ import OpenAI from 'openai' export const runtime = 'edge' /* To avoid OpenAI errors, restrict to the Vercel Edge Function regions that overlap with the OpenAI API regions. - + Reference for Vercel regions: https://vercel.com/docs/edge-network/regions#region-list Reference for OpenAI regions: https://help.openai.com/en/articles/5347006-openai-api-supported-countries-and-territories */ diff --git a/apps/docs/app/contributing/content.mdx b/apps/docs/app/contributing/content.mdx index 12cba239f87..a185cff8895 100644 --- a/apps/docs/app/contributing/content.mdx +++ b/apps/docs/app/contributing/content.mdx @@ -167,7 +167,7 @@ const PI = 3.14 ``` ```` -Of, you can use the `<$CodeSample />` component to include code samples from a source code file. +Or, you can use the `<$CodeSample />` component to include code samples from a source code file. If the file is within the `supabase/supabase` repo's `examples` directory: @@ -183,7 +183,7 @@ meta="display/path.js" /> ``` -If the file is within some other GitHub repo (note that the repo must be public): +If the file is within some other GitHub repo: ```mdx <$CodeSample @@ -197,6 +197,8 @@ meta="display/path.js" /> ``` +The repo must be public, the org must be on the allow list, and the commit must be an immutable SHA (not a mutable tag or branch name). + ### Icons The following icons are available. They can be styled with [Tailwind](https://tailwindcss.com/) classes: diff --git a/apps/docs/app/contributing/page.tsx b/apps/docs/app/contributing/page.tsx index 666836b4d07..851706f228a 100644 --- a/apps/docs/app/contributing/page.tsx +++ b/apps/docs/app/contributing/page.tsx @@ -12,7 +12,7 @@ export default async function ContributingPage() { const content = await readFile(contentFile, 'utf-8') return ( - +
diff --git a/apps/docs/app/guides/(with-sidebar)/[...slug]/page.tsx b/apps/docs/app/guides/(with-sidebar)/[...slug]/page.tsx deleted file mode 100644 index 2bbfc101eea..00000000000 --- a/apps/docs/app/guides/(with-sidebar)/[...slug]/page.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import { - getGuidesMarkdown, - genGuideMeta, - genGuidesStaticParams, -} from '~/features/docs/GuidesMdx.utils' -import { GuideTemplate } from '~/features/docs/GuidesMdx.template' - -type Params = { slug: string[] } - -const GuidePage = async ({ params }: { params: Params }) => { - const data = await getGuidesMarkdown(params) - return -} - -const generateStaticParams = genGuidesStaticParams() -const generateMetadata = genGuideMeta(getGuidesMarkdown) - -export default GuidePage -export { generateStaticParams, generateMetadata } diff --git a/apps/docs/app/guides/(with-sidebar)/layout.tsx b/apps/docs/app/guides/(with-sidebar)/layout.tsx deleted file mode 100644 index 8df012ac97c..00000000000 --- a/apps/docs/app/guides/(with-sidebar)/layout.tsx +++ /dev/null @@ -1,16 +0,0 @@ -'use client' - -import { usePathname } from 'next/navigation' -import { type PropsWithChildren } from 'react' - -import { getMenuId } from '~/components/Navigation/NavigationMenu/NavigationMenu.utils' -import Layout from '~/layouts/guides' - -const GuidesLayout = ({ children }: PropsWithChildren) => { - const pathname = usePathname() - const menuId = getMenuId(pathname) - - return {children} -} - -export default GuidesLayout diff --git a/apps/docs/app/guides/ai/[[...slug]]/page.tsx b/apps/docs/app/guides/ai/[[...slug]]/page.tsx new file mode 100644 index 00000000000..50198e532f3 --- /dev/null +++ b/apps/docs/app/guides/ai/[[...slug]]/page.tsx @@ -0,0 +1,25 @@ +import { + getGuidesMarkdown, + genGuideMeta, + genGuidesStaticParams, +} from '~/features/docs/GuidesMdx.utils' +import { GuideTemplate } from '~/features/docs/GuidesMdx.template' + +export const dynamicParams = false + +type Params = { slug?: string[] } + +const AiGuidePage = async ({ params }: { params: Params }) => { + const slug = ['ai', ...(params.slug ?? [])] + const data = await getGuidesMarkdown(slug) + + return +} + +const generateStaticParams = genGuidesStaticParams('ai') +const generateMetadata = genGuideMeta((params: { slug?: string[] }) => + getGuidesMarkdown(['ai', ...(params.slug ?? [])]) +) + +export default AiGuidePage +export { generateStaticParams, generateMetadata } diff --git a/apps/docs/app/guides/ai/layout.tsx b/apps/docs/app/guides/ai/layout.tsx new file mode 100644 index 00000000000..8964ce1c3a4 --- /dev/null +++ b/apps/docs/app/guides/ai/layout.tsx @@ -0,0 +1,5 @@ +import Layout from '~/layouts/guides' + +export default async function AiLayout({ children }: { children: React.ReactNode }) { + return {children} +} diff --git a/apps/docs/app/guides/(with-sidebar)/ai/python/[slug]/page.tsx b/apps/docs/app/guides/ai/python/[slug]/page.tsx similarity index 93% rename from apps/docs/app/guides/(with-sidebar)/ai/python/[slug]/page.tsx rename to apps/docs/app/guides/ai/python/[slug]/page.tsx index 578df9c3119..8ebfa5dde15 100644 --- a/apps/docs/app/guides/(with-sidebar)/ai/python/[slug]/page.tsx +++ b/apps/docs/app/guides/ai/python/[slug]/page.tsx @@ -1,15 +1,17 @@ -import { type SerializeOptions } from 'next-mdx-remote/dist/types' +import type { SerializeOptions } from 'next-mdx-remote/dist/types' import { notFound } from 'next/navigation' import { relative } from 'path' import rehypeSlug from 'rehype-slug' -import { genGuideMeta } from '~/features/docs/GuidesMdx.utils' +import { genGuideMeta, removeRedundantH1 } from '~/features/docs/GuidesMdx.utils' import { GuideTemplate, newEditLink } from '~/features/docs/GuidesMdx.template' import { fetchRevalidatePerDay } from '~/features/helpers.fetch' import { UrlTransformFunction, linkTransform } from '~/lib/mdx/plugins/rehypeLinkTransform' import remarkMkDocsAdmonition from '~/lib/mdx/plugins/remarkAdmonition' import { removeTitle } from '~/lib/mdx/plugins/remarkRemoveTitle' +export const dynamicParams = false + // We fetch these docs at build time from an external repo const org = 'supabase' const repo = 'vecs' @@ -84,7 +86,8 @@ const getContent = async ({ slug }: Params) => { `https://raw.githubusercontent.com/${org}/${repo}/${branch}/${docsDir}/${remoteFile}` ) - const content = await response.text() + let content = await response.text() + content = removeRedundantH1(content) return { pathname: `/guides/ai/python/${slug}` satisfies `/${string}`, diff --git a/apps/docs/app/guides/api/[[...slug]]/page.tsx b/apps/docs/app/guides/api/[[...slug]]/page.tsx new file mode 100644 index 00000000000..85ec5923214 --- /dev/null +++ b/apps/docs/app/guides/api/[[...slug]]/page.tsx @@ -0,0 +1,25 @@ +import { + getGuidesMarkdown, + genGuideMeta, + genGuidesStaticParams, +} from '~/features/docs/GuidesMdx.utils' +import { GuideTemplate } from '~/features/docs/GuidesMdx.template' + +export const dynamicParams = false + +type Params = { slug?: string[] } + +const ApiGuidePage = async ({ params }: { params: Params }) => { + const slug = ['api', ...(params.slug ?? [])] + const data = await getGuidesMarkdown(slug) + + return +} + +const generateStaticParams = genGuidesStaticParams('api') +const generateMetadata = genGuideMeta((params: { slug?: string[] }) => + getGuidesMarkdown(['api', ...(params.slug ?? [])]) +) + +export default ApiGuidePage +export { generateStaticParams, generateMetadata } diff --git a/apps/docs/app/guides/api/layout.tsx b/apps/docs/app/guides/api/layout.tsx new file mode 100644 index 00000000000..cae6afe2d43 --- /dev/null +++ b/apps/docs/app/guides/api/layout.tsx @@ -0,0 +1,5 @@ +import Layout from '~/layouts/guides' + +export default async function ApiLayout({ children }: { children: React.ReactNode }) { + return {children} +} diff --git a/apps/docs/app/guides/auth/[[...slug]]/page.tsx b/apps/docs/app/guides/auth/[[...slug]]/page.tsx new file mode 100644 index 00000000000..ddadcad5a12 --- /dev/null +++ b/apps/docs/app/guides/auth/[[...slug]]/page.tsx @@ -0,0 +1,25 @@ +import { + getGuidesMarkdown, + genGuideMeta, + genGuidesStaticParams, +} from '~/features/docs/GuidesMdx.utils' +import { GuideTemplate } from '~/features/docs/GuidesMdx.template' + +export const dynamicParams = false + +type Params = { slug?: string[] } + +const AuthGuidePage = async ({ params }: { params: Params }) => { + const slug = ['auth', ...(params.slug ?? [])] + const data = await getGuidesMarkdown(slug) + + return +} + +const generateStaticParams = genGuidesStaticParams('auth') +const generateMetadata = genGuideMeta((params: { slug?: string[] }) => + getGuidesMarkdown(['auth', ...(params.slug ?? [])]) +) + +export default AuthGuidePage +export { generateStaticParams, generateMetadata } diff --git a/apps/docs/app/guides/auth/layout.tsx b/apps/docs/app/guides/auth/layout.tsx new file mode 100644 index 00000000000..a7166e638ab --- /dev/null +++ b/apps/docs/app/guides/auth/layout.tsx @@ -0,0 +1,5 @@ +import Layout from '~/layouts/guides' + +export default async function AuthLayout({ children }: { children: React.ReactNode }) { + return {children} +} diff --git a/apps/docs/app/guides/cron/[[...slug]]/page.tsx b/apps/docs/app/guides/cron/[[...slug]]/page.tsx new file mode 100644 index 00000000000..fb55f0fac85 --- /dev/null +++ b/apps/docs/app/guides/cron/[[...slug]]/page.tsx @@ -0,0 +1,25 @@ +import { + getGuidesMarkdown, + genGuideMeta, + genGuidesStaticParams, +} from '~/features/docs/GuidesMdx.utils' +import { GuideTemplate } from '~/features/docs/GuidesMdx.template' + +export const dynamicParams = false + +type Params = { slug?: string[] } + +const CronGuidePage = async ({ params }: { params: Params }) => { + const slug = ['cron', ...(params.slug ?? [])] + const data = await getGuidesMarkdown(slug) + + return +} + +const generateStaticParams = genGuidesStaticParams('cron') +const generateMetadata = genGuideMeta((params: { slug?: string[] }) => + getGuidesMarkdown(['cron', ...(params.slug ?? [])]) +) + +export default CronGuidePage +export { generateStaticParams, generateMetadata } diff --git a/apps/docs/app/guides/cron/layout.tsx b/apps/docs/app/guides/cron/layout.tsx new file mode 100644 index 00000000000..23446ce2fef --- /dev/null +++ b/apps/docs/app/guides/cron/layout.tsx @@ -0,0 +1,5 @@ +import Layout from '~/layouts/guides' + +export default async function CronLayout({ children }: { children: React.ReactNode }) { + return {children} +} diff --git a/apps/docs/app/guides/database/[[...slug]]/page.tsx b/apps/docs/app/guides/database/[[...slug]]/page.tsx new file mode 100644 index 00000000000..0f92d872d92 --- /dev/null +++ b/apps/docs/app/guides/database/[[...slug]]/page.tsx @@ -0,0 +1,25 @@ +import { + getGuidesMarkdown, + genGuideMeta, + genGuidesStaticParams, +} from '~/features/docs/GuidesMdx.utils' +import { GuideTemplate } from '~/features/docs/GuidesMdx.template' + +export const dynamicParams = false + +type Params = { slug?: string[] } + +const DatabaseGuidePage = async ({ params }: { params: Params }) => { + const slug = ['database', ...(params.slug ?? [])] + const data = await getGuidesMarkdown(slug) + + return +} + +const generateStaticParams = genGuidesStaticParams('database') +const generateMetadata = genGuideMeta((params: { slug?: string[] }) => + getGuidesMarkdown(['database', ...(params.slug ?? [])]) +) + +export default DatabaseGuidePage +export { generateStaticParams, generateMetadata } diff --git a/apps/docs/app/guides/(with-sidebar)/database/database-advisors/page.tsx b/apps/docs/app/guides/database/database-advisors/page.tsx similarity index 100% rename from apps/docs/app/guides/(with-sidebar)/database/database-advisors/page.tsx rename to apps/docs/app/guides/database/database-advisors/page.tsx diff --git a/apps/docs/app/guides/(with-sidebar)/database/extensions/wrappers/[[...slug]]/page.tsx b/apps/docs/app/guides/database/extensions/wrappers/[[...slug]]/page.tsx similarity index 87% rename from apps/docs/app/guides/(with-sidebar)/database/extensions/wrappers/[[...slug]]/page.tsx rename to apps/docs/app/guides/database/extensions/wrappers/[[...slug]]/page.tsx index 16a6e7ce50e..ca59f1707c2 100644 --- a/apps/docs/app/guides/(with-sidebar)/database/extensions/wrappers/[[...slug]]/page.tsx +++ b/apps/docs/app/guides/database/extensions/wrappers/[[...slug]]/page.tsx @@ -1,14 +1,15 @@ import matter from 'gray-matter' -import { type Heading } from 'mdast' -import { fromMarkdown } from 'mdast-util-from-markdown' -import { toMarkdown } from 'mdast-util-to-markdown' import { type SerializeOptions } from 'next-mdx-remote/dist/types' import { readFile } from 'node:fs/promises' import { join, relative } from 'node:path' import rehypeSlug from 'rehype-slug' import emoji from 'remark-emoji' -import { genGuideMeta, genGuidesStaticParams } from '~/features/docs/GuidesMdx.utils' +import { + genGuideMeta, + genGuidesStaticParams, + removeRedundantH1, +} from '~/features/docs/GuidesMdx.utils' import { GuideTemplate, newEditLink } from '~/features/docs/GuidesMdx.template' import { fetchRevalidatePerDay } from '~/features/helpers.fetch' import { GUIDES_DIRECTORY, isValidGuideFrontmatter } from '~/lib/docs' @@ -16,7 +17,8 @@ import { UrlTransformFunction, linkTransform } from '~/lib/mdx/plugins/rehypeLin import remarkMkDocsAdmonition from '~/lib/mdx/plugins/remarkAdmonition' import { removeTitle } from '~/lib/mdx/plugins/remarkRemoveTitle' import remarkPyMdownTabs from '~/lib/mdx/plugins/remarkTabs' -import remarkGfm from 'remark-gfm' + +export const dynamicParams = false // We fetch these docs at build time from an external repo const org = 'supabase' @@ -184,22 +186,7 @@ const getContent = async (params: Params) => { const rawContent = await response.text() const { content: contentWithoutFrontmatter } = matter(rawContent) - - // This is the more robust way of doing it, but problems with the rewritten - // Markdown and handling of tables this way, so saving it for later. - // - // const mdxTree = fromMarkdown(contentWithoutFrontmatter) - // const maybeH1 = mdxTree.children[0] - // if (maybeH1 && maybeH1.type === 'heading' && (maybeH1 as Heading).depth === 1) { - // mdxTree.children.shift() - // } - // content = toMarkdown(mdxTree) - - content = contentWithoutFrontmatter - if (meta.title) { - const h1Regex = new RegExp(`(?:^|\n)# ${meta.title}\n+`) - content = content.replace(h1Regex, '') - } + content = removeRedundantH1(contentWithoutFrontmatter) } return { diff --git a/apps/docs/app/guides/database/layout.tsx b/apps/docs/app/guides/database/layout.tsx new file mode 100644 index 00000000000..530c23748d5 --- /dev/null +++ b/apps/docs/app/guides/database/layout.tsx @@ -0,0 +1,5 @@ +import Layout from '~/layouts/guides' + +export default async function DatabaseLayout({ children }: { children: React.ReactNode }) { + return {children} +} diff --git a/apps/docs/app/guides/deployment/[[...slug]]/page.tsx b/apps/docs/app/guides/deployment/[[...slug]]/page.tsx new file mode 100644 index 00000000000..5c4311b6ede --- /dev/null +++ b/apps/docs/app/guides/deployment/[[...slug]]/page.tsx @@ -0,0 +1,25 @@ +import { + getGuidesMarkdown, + genGuideMeta, + genGuidesStaticParams, +} from '~/features/docs/GuidesMdx.utils' +import { GuideTemplate } from '~/features/docs/GuidesMdx.template' + +export const dynamicParams = false + +type Params = { slug?: string[] } + +const DeploymentGuidePage = async ({ params }: { params: Params }) => { + const slug = ['deployment', ...(params.slug ?? [])] + const data = await getGuidesMarkdown(slug) + + return +} + +const generateStaticParams = genGuidesStaticParams('deployment') +const generateMetadata = genGuideMeta((params: { slug?: string[] }) => + getGuidesMarkdown(['deployment', ...(params.slug ?? [])]) +) + +export default DeploymentGuidePage +export { generateStaticParams, generateMetadata } diff --git a/apps/docs/app/guides/(with-sidebar)/cli/github-action/[slug]/page.tsx b/apps/docs/app/guides/deployment/ci/[slug]/page.tsx similarity index 95% rename from apps/docs/app/guides/(with-sidebar)/cli/github-action/[slug]/page.tsx rename to apps/docs/app/guides/deployment/ci/[slug]/page.tsx index 2f229fbc8f9..c505be8f3bf 100644 --- a/apps/docs/app/guides/(with-sidebar)/cli/github-action/[slug]/page.tsx +++ b/apps/docs/app/guides/deployment/ci/[slug]/page.tsx @@ -3,7 +3,7 @@ import { notFound } from 'next/navigation' import { relative } from 'node:path' import rehypeSlug from 'rehype-slug' -import { genGuideMeta } from '~/features/docs/GuidesMdx.utils' +import { genGuideMeta, removeRedundantH1 } from '~/features/docs/GuidesMdx.utils' import { GuideTemplate, newEditLink } from '~/features/docs/GuidesMdx.template' import { fetchRevalidatePerDay } from '~/features/helpers.fetch' import { UrlTransformFunction, linkTransform } from '~/lib/mdx/plugins/rehypeLinkTransform' @@ -11,6 +11,8 @@ import remarkMkDocsAdmonition from '~/lib/mdx/plugins/remarkAdmonition' import { removeTitle } from '~/lib/mdx/plugins/remarkRemoveTitle' import remarkPyMdownTabs from '~/lib/mdx/plugins/remarkTabs' +export const dynamicParams = false + // We fetch these docs at build time from an external repo const org = 'supabase' const repo = 'setup-cli' @@ -83,7 +85,7 @@ const getContent = async ({ slug }: Params) => { `https://raw.githubusercontent.com/${org}/${repo}/${branch}/${docsDir}/${remoteFile}` ) - const content = await response.text() + const content = removeRedundantH1(await response.text()) return { pathname: `/guides/cli/github-action/${slug}` satisfies `/${string}`, diff --git a/apps/docs/app/guides/deployment/layout.tsx b/apps/docs/app/guides/deployment/layout.tsx new file mode 100644 index 00000000000..aa230536e47 --- /dev/null +++ b/apps/docs/app/guides/deployment/layout.tsx @@ -0,0 +1,5 @@ +import Layout from '~/layouts/guides' + +export default async function DeploymentLayout({ children }: { children: React.ReactNode }) { + return {children} +} diff --git a/apps/docs/app/guides/(with-sidebar)/platform/terraform/[[...slug]]/page.tsx b/apps/docs/app/guides/deployment/terraform/[[...slug]]/page.tsx similarity index 93% rename from apps/docs/app/guides/(with-sidebar)/platform/terraform/[[...slug]]/page.tsx rename to apps/docs/app/guides/deployment/terraform/[[...slug]]/page.tsx index 489f1d71095..bd8e51bde05 100644 --- a/apps/docs/app/guides/(with-sidebar)/platform/terraform/[[...slug]]/page.tsx +++ b/apps/docs/app/guides/deployment/terraform/[[...slug]]/page.tsx @@ -3,7 +3,7 @@ import { type SerializeOptions } from 'next-mdx-remote/dist/types' import { notFound } from 'next/navigation' import rehypeSlug from 'rehype-slug' -import { genGuideMeta } from '~/features/docs/GuidesMdx.utils' +import { genGuideMeta, removeRedundantH1 } from '~/features/docs/GuidesMdx.utils' import { GuideTemplate, newEditLink } from '~/features/docs/GuidesMdx.template' import { fetchRevalidatePerDay } from '~/features/helpers.fetch' import { isValidGuideFrontmatter } from '~/lib/docs' @@ -18,6 +18,8 @@ import { terraformDocsRepo, } from '../terraformConstants' +export const dynamicParams = false + // Each external docs page is mapped to a local page const pageMap = [ { @@ -119,7 +121,11 @@ const getContent = async ({ slug }: Params) => { let rawContent = await response.text() // Strip out HTML comments rawContent = rawContent.replace(//, '') - const { content, data } = matter(rawContent) + let { content, data } = matter(rawContent) + + // Remove the title from the content so it isn't duplicated in the final display + content = removeRedundantH1(content) + Object.assign(meta, data) if (!isValidGuideFrontmatter(meta)) { diff --git a/apps/docs/app/guides/(with-sidebar)/platform/terraform/reference/page.tsx b/apps/docs/app/guides/deployment/terraform/reference/page.tsx similarity index 100% rename from apps/docs/app/guides/(with-sidebar)/platform/terraform/reference/page.tsx rename to apps/docs/app/guides/deployment/terraform/reference/page.tsx diff --git a/apps/docs/app/guides/(with-sidebar)/platform/terraform/terraformConstants.ts b/apps/docs/app/guides/deployment/terraform/terraformConstants.ts similarity index 100% rename from apps/docs/app/guides/(with-sidebar)/platform/terraform/terraformConstants.ts rename to apps/docs/app/guides/deployment/terraform/terraformConstants.ts diff --git a/apps/docs/app/guides/functions/[[...slug]]/page.tsx b/apps/docs/app/guides/functions/[[...slug]]/page.tsx new file mode 100644 index 00000000000..e6f2c65a84b --- /dev/null +++ b/apps/docs/app/guides/functions/[[...slug]]/page.tsx @@ -0,0 +1,25 @@ +import { + getGuidesMarkdown, + genGuideMeta, + genGuidesStaticParams, +} from '~/features/docs/GuidesMdx.utils' +import { GuideTemplate } from '~/features/docs/GuidesMdx.template' + +export const dynamicParams = false + +type Params = { slug?: string[] } + +const FunctionsGuidePage = async ({ params }: { params: Params }) => { + const slug = ['functions', ...(params.slug ?? [])] + const data = await getGuidesMarkdown(slug) + + return +} + +const generateStaticParams = genGuidesStaticParams('functions') +const generateMetadata = genGuideMeta((params: { slug?: string[] }) => + getGuidesMarkdown(['functions', ...(params.slug ?? [])]) +) + +export default FunctionsGuidePage +export { generateStaticParams, generateMetadata } diff --git a/apps/docs/app/guides/functions/layout.tsx b/apps/docs/app/guides/functions/layout.tsx new file mode 100644 index 00000000000..b2fc2296797 --- /dev/null +++ b/apps/docs/app/guides/functions/layout.tsx @@ -0,0 +1,5 @@ +import Layout from '~/layouts/guides' + +export default async function FunctionsLayout({ children }: { children: React.ReactNode }) { + return {children} +} diff --git a/apps/docs/app/guides/getting-started/[[...slug]]/page.tsx b/apps/docs/app/guides/getting-started/[[...slug]]/page.tsx new file mode 100644 index 00000000000..429055c817a --- /dev/null +++ b/apps/docs/app/guides/getting-started/[[...slug]]/page.tsx @@ -0,0 +1,25 @@ +import { + getGuidesMarkdown, + genGuideMeta, + genGuidesStaticParams, +} from '~/features/docs/GuidesMdx.utils' +import { GuideTemplate } from '~/features/docs/GuidesMdx.template' + +export const dynamicParams = false + +type Params = { slug?: string[] } + +const GettingStartedGuidePage = async ({ params }: { params: Params }) => { + const slug = ['getting-started', ...(params.slug ?? [])] + const data = await getGuidesMarkdown(slug) + + return +} + +const generateStaticParams = genGuidesStaticParams('getting-started') +const generateMetadata = genGuideMeta((params: { slug?: string[] }) => + getGuidesMarkdown(['getting-started', ...(params.slug ?? [])]) +) + +export default GettingStartedGuidePage +export { generateStaticParams, generateMetadata } diff --git a/apps/docs/app/guides/getting-started/ai-prompts/[slug]/AiPrompts.utils.ts b/apps/docs/app/guides/getting-started/ai-prompts/[slug]/AiPrompts.utils.ts new file mode 100644 index 00000000000..0c8f83d7f0c --- /dev/null +++ b/apps/docs/app/guides/getting-started/ai-prompts/[slug]/AiPrompts.utils.ts @@ -0,0 +1,98 @@ +import matter from 'gray-matter' +import type { Code, Heading, Root } from 'mdast' +import { fromMarkdown } from 'mdast-util-from-markdown' +import { toMarkdown } from 'mdast-util-to-markdown' +import { readdir, readFile, stat } from 'node:fs/promises' +import { basename, extname, join } from 'node:path' +import { cache } from 'react' +import { visit, EXIT } from 'unist-util-visit' + +import { EXAMPLES_DIRECTORY } from '~/lib/docs' + +const PROMPTS_DIRECTORY = join(EXAMPLES_DIRECTORY, 'prompts') + +function parseMarkdown(markdown: string) { + const { content: withoutFrontmatter } = matter(markdown) + const mdast = fromMarkdown(withoutFrontmatter) + + let heading = '' + visit(mdast, 'heading', (node: Heading) => { + if (node.depth === 1) { + if ('value' in node.children[0]) { + heading = node.children[0].value + } + return EXIT + } + }) + + const codeBlock: Code = { + type: 'code', + lang: 'markdown', + value: markdown, + } + const root: Root = { + type: 'root', + children: [codeBlock], + } + const content = toMarkdown(root) + + return { heading, content } +} + +async function getAiPromptsImpl() { + const directoryContents = await readdir(PROMPTS_DIRECTORY) + + const prompts = directoryContents + .filter(async (file) => { + if (extname(file) !== '.md') { + return false + } + + const fileStats = await stat(join(PROMPTS_DIRECTORY, file)) + const isFile = fileStats.isFile() + return isFile + }) + .map(async (filename) => { + const rawContent = await readFile(join(PROMPTS_DIRECTORY, filename), 'utf-8') + const { heading, content } = parseMarkdown(rawContent) + + return { + filename: basename(filename, '.md'), + heading, + content, + } + }) + + return (await Promise.all(prompts)).sort((a, b) => b.filename.localeCompare(a.filename)) +} +export const getAiPrompts = cache(getAiPromptsImpl) + +async function getAiPromptImpl(prompt: string) { + const filePath = join(PROMPTS_DIRECTORY, `${prompt}.md`) + try { + const rawContent = await readFile(filePath, 'utf-8') + const { heading, content } = parseMarkdown(rawContent) + return { heading, content } + } catch (err) { + console.error('Failed to fetch prompt from repo: %o', err) + } +} +export const getAiPrompt = cache(getAiPromptImpl) + +export async function generateAiPromptMetadata({ params: { slug } }: { params: { slug: string } }) { + const prompt = await getAiPrompt(slug) + + return { + title: `AI Prompt: ${prompt.heading} | Supabase Docs`, + } +} + +export async function generateAiPromptsStaticParams() { + const prompts = await getAiPrompts() + + return prompts.map((prompt) => { + return { + slug: prompt.filename, + } + }) +} diff --git a/apps/docs/app/guides/getting-started/ai-prompts/[slug]/AiPromptsIndex.tsx b/apps/docs/app/guides/getting-started/ai-prompts/[slug]/AiPromptsIndex.tsx new file mode 100644 index 00000000000..ed32afb8c67 --- /dev/null +++ b/apps/docs/app/guides/getting-started/ai-prompts/[slug]/AiPromptsIndex.tsx @@ -0,0 +1,26 @@ +import Link from 'next/link' + +import { GlassPanel } from 'ui-patterns' + +import { getAiPrompts } from './AiPrompts.utils' + +export async function AiPromptsIndex() { + const prompts = await getAiPrompts() + + return ( +
+ {prompts.map((prompt) => ( + + + + ))} +
+ ) +} diff --git a/apps/docs/app/guides/getting-started/ai-prompts/[slug]/page.tsx b/apps/docs/app/guides/getting-started/ai-prompts/[slug]/page.tsx new file mode 100644 index 00000000000..d098909dfd9 --- /dev/null +++ b/apps/docs/app/guides/getting-started/ai-prompts/[slug]/page.tsx @@ -0,0 +1,34 @@ +import { GuideTemplate, newEditLink } from '~/features/docs/GuidesMdx.template' +import { + generateAiPromptMetadata, + generateAiPromptsStaticParams, + getAiPrompt, +} from './AiPrompts.utils' + +export const dynamicParams = false + +export default async function AiPromptsPage({ params: { slug } }: { params: { slug: string } }) { + let { heading, content } = await getAiPrompt(slug) + content = ` +## How to use + +Copy the prompt to a file in your repo. + +Use the "include file" feature from your AI tool to include the prompt when chatting with your AI assistant. For example, with GitHub Copilot, use \`#\`, in Cursor, use \`@Files\`, and in Zed, use \`/file\`. + +## Prompt + +${content} +`.trim() + + return ( + + ) +} + +export const generateMetadata = generateAiPromptMetadata +export const generateStaticParams = generateAiPromptsStaticParams diff --git a/apps/docs/app/guides/getting-started/layout.tsx b/apps/docs/app/guides/getting-started/layout.tsx new file mode 100644 index 00000000000..ba83678f3b7 --- /dev/null +++ b/apps/docs/app/guides/getting-started/layout.tsx @@ -0,0 +1,20 @@ +import { type NavMenuSection } from '~/components/Navigation/Navigation.types' +import Layout from '~/layouts/guides' +import { getAiPrompts } from '../getting-started/ai-prompts/[slug]/AiPrompts.utils' + +export default async function GettingStartedLayout({ children }: { children: React.ReactNode }) { + const additionalNavItems = { prompts: await getPrompts() } + + return {children} +} + +async function getPrompts() { + const prompts = await getAiPrompts() + return prompts.map( + (prompt) => + ({ + name: prompt.heading, + url: `/guides/getting-started/ai-prompts/${prompt.filename}`, + }) as Partial + ) +} diff --git a/apps/docs/app/guides/(with-sidebar)/graphql/[[...slug]]/page.tsx b/apps/docs/app/guides/graphql/[[...slug]]/page.tsx similarity index 96% rename from apps/docs/app/guides/(with-sidebar)/graphql/[[...slug]]/page.tsx rename to apps/docs/app/guides/graphql/[[...slug]]/page.tsx index aaf53857d1f..fbf0d930735 100644 --- a/apps/docs/app/guides/(with-sidebar)/graphql/[[...slug]]/page.tsx +++ b/apps/docs/app/guides/graphql/[[...slug]]/page.tsx @@ -5,12 +5,14 @@ import rehypeSlug from 'rehype-slug' import { genGuideMeta } from '~/features/docs/GuidesMdx.utils' import { GuideTemplate, newEditLink } from '~/features/docs/GuidesMdx.template' -import { fetchRevalidatePerDay } from '~/features/helpers.fetch' +import { fetchRevalidatePerDay_TEMP_TESTING } from '~/features/helpers.fetch' import { UrlTransformFunction, linkTransform } from '~/lib/mdx/plugins/rehypeLinkTransform' import remarkMkDocsAdmonition from '~/lib/mdx/plugins/remarkAdmonition' import { removeTitle } from '~/lib/mdx/plugins/remarkRemoveTitle' import remarkPyMdownTabs from '~/lib/mdx/plugins/remarkTabs' +export const dynamicParams = false + // We fetch these docs at build time from an external repo const org = 'supabase' const repo = 'pg_graphql' @@ -133,7 +135,7 @@ const getContent = async ({ slug }: Params) => { const editLink = newEditLink(`${org}/${repo}/blob/${branch}/${docsDir}/${remoteFile}`) - const response = await fetchRevalidatePerDay( + const response = await fetchRevalidatePerDay_TEMP_TESTING( `https://raw.githubusercontent.com/${org}/${repo}/${branch}/${docsDir}/${remoteFile}` ) diff --git a/apps/docs/app/guides/graphql/layout.tsx b/apps/docs/app/guides/graphql/layout.tsx new file mode 100644 index 00000000000..59db0623dac --- /dev/null +++ b/apps/docs/app/guides/graphql/layout.tsx @@ -0,0 +1,5 @@ +import Layout from '~/layouts/guides' + +export default async function GraphQlLayout({ children }: { children: React.ReactNode }) { + return {children} +} diff --git a/apps/docs/app/guides/integrations/[[...slug]]/page.tsx b/apps/docs/app/guides/integrations/[[...slug]]/page.tsx new file mode 100644 index 00000000000..34f193e076b --- /dev/null +++ b/apps/docs/app/guides/integrations/[[...slug]]/page.tsx @@ -0,0 +1,25 @@ +import { + getGuidesMarkdown, + genGuideMeta, + genGuidesStaticParams, +} from '~/features/docs/GuidesMdx.utils' +import { GuideTemplate } from '~/features/docs/GuidesMdx.template' + +export const dynamicParams = false + +type Params = { slug?: string[] } + +const IntegrationsGuidePage = async ({ params }: { params: Params }) => { + const slug = ['integrations', ...(params.slug ?? [])] + const data = await getGuidesMarkdown(slug) + + return +} + +const generateStaticParams = genGuidesStaticParams('integrations') +const generateMetadata = genGuideMeta((params: { slug?: string[] }) => + getGuidesMarkdown(['integrations', ...(params.slug ?? [])]) +) + +export default IntegrationsGuidePage +export { generateStaticParams, generateMetadata } diff --git a/apps/docs/app/guides/integrations/layout.tsx b/apps/docs/app/guides/integrations/layout.tsx new file mode 100644 index 00000000000..7b5943f2253 --- /dev/null +++ b/apps/docs/app/guides/integrations/layout.tsx @@ -0,0 +1,39 @@ +import { IS_PLATFORM } from 'common' +import { unstable_cache } from 'next/cache' + +import { type NavMenuSection } from '~/components/Navigation/Navigation.types' +import Layout from '~/layouts/guides' +import { supabaseMisc } from '~/lib/supabaseMisc' + +export default async function IntegrationsLayout({ children }: { children: React.ReactNode }) { + const additionalNavItems = { integrations: await getPartners() } + + return {children} +} + +// Will need to turn on revalidation later, just turning it off for now so we +// can slowly turn things back on while monitoring usage +const getPartners = unstable_cache(getPartnersImpl, [], { revalidate: false }) +async function getPartnersImpl() { + if (!IS_PLATFORM) return [] + + const { data, error } = await supabaseMisc() + .from('partners') + .select('slug, title') + .eq('approved', true) + .eq('type', 'technology') + .order('title') + if (error) { + console.error(new Error('Error fetching partners', { cause: error })) + } + + const partnerNavItems = (data ?? []).map( + (partner) => + ({ + name: partner.title, + url: `https://supabase.com/partners/integrations/${partner.slug}` as `https://${string}`, + }) as Partial + ) + + return partnerNavItems +} diff --git a/apps/docs/app/guides/local-development/[[...slug]]/page.tsx b/apps/docs/app/guides/local-development/[[...slug]]/page.tsx new file mode 100644 index 00000000000..1bd6814e8c1 --- /dev/null +++ b/apps/docs/app/guides/local-development/[[...slug]]/page.tsx @@ -0,0 +1,25 @@ +import { + getGuidesMarkdown, + genGuideMeta, + genGuidesStaticParams, +} from '~/features/docs/GuidesMdx.utils' +import { GuideTemplate } from '~/features/docs/GuidesMdx.template' + +export const dynamicParams = false + +type Params = { slug?: string[] } + +const LocalDevelopmentGuidePage = async ({ params }: { params: Params }) => { + const slug = ['local-development', ...(params.slug ?? [])] + const data = await getGuidesMarkdown(slug) + + return +} + +const generateStaticParams = genGuidesStaticParams('local-development') +const generateMetadata = genGuideMeta((params: { slug?: string[] }) => + getGuidesMarkdown(['local-development', ...(params.slug ?? [])]) +) + +export default LocalDevelopmentGuidePage +export { generateStaticParams, generateMetadata } diff --git a/apps/docs/app/guides/(with-sidebar)/cli/config/page.tsx b/apps/docs/app/guides/local-development/cli/config/page.tsx similarity index 98% rename from apps/docs/app/guides/(with-sidebar)/cli/config/page.tsx rename to apps/docs/app/guides/local-development/cli/config/page.tsx index 4707208e7e0..9ce99b1da78 100644 --- a/apps/docs/app/guides/(with-sidebar)/cli/config/page.tsx +++ b/apps/docs/app/guides/local-development/cli/config/page.tsx @@ -5,7 +5,7 @@ import { type TOCHeader } from '~/components/GuidesTableOfContents' import { genGuideMeta } from '~/features/docs/GuidesMdx.utils' import { GuideTemplate, newEditLink } from '~/features/docs/GuidesMdx.template' import type { Parameter } from '~/lib/refGenerator/refTypes' -import specFile from '~/spec/cli_v1_config.yaml' assert { type: 'yml' } +import specFile from '~/spec/cli_v1_config.yaml' with { type: 'yml' } const meta = { title: 'Supabase CLI config', diff --git a/apps/docs/app/guides/local-development/layout.tsx b/apps/docs/app/guides/local-development/layout.tsx new file mode 100644 index 00000000000..b73f4d90a5c --- /dev/null +++ b/apps/docs/app/guides/local-development/layout.tsx @@ -0,0 +1,5 @@ +import Layout from '~/layouts/guides' + +export default async function LocalDevelopmentLayout({ children }: { children: React.ReactNode }) { + return {children} +} diff --git a/apps/docs/app/guides/platform/[[...slug]]/page.tsx b/apps/docs/app/guides/platform/[[...slug]]/page.tsx new file mode 100644 index 00000000000..0c76803590a --- /dev/null +++ b/apps/docs/app/guides/platform/[[...slug]]/page.tsx @@ -0,0 +1,25 @@ +import { + getGuidesMarkdown, + genGuideMeta, + genGuidesStaticParams, +} from '~/features/docs/GuidesMdx.utils' +import { GuideTemplate } from '~/features/docs/GuidesMdx.template' + +export const dynamicParams = false + +type Params = { slug?: string[] } + +const PlatformGuidePage = async ({ params }: { params: Params }) => { + const slug = ['platform', ...(params.slug ?? [])] + const data = await getGuidesMarkdown(slug) + + return +} + +const generateStaticParams = genGuidesStaticParams('platform') +const generateMetadata = genGuideMeta((params: { slug?: string[] }) => + getGuidesMarkdown(['platform', ...(params.slug ?? [])]) +) + +export default PlatformGuidePage +export { generateStaticParams, generateMetadata } diff --git a/apps/docs/app/guides/platform/layout.tsx b/apps/docs/app/guides/platform/layout.tsx new file mode 100644 index 00000000000..5af6fa712c6 --- /dev/null +++ b/apps/docs/app/guides/platform/layout.tsx @@ -0,0 +1,5 @@ +import Layout from '~/layouts/guides' + +export default async function PlatformLayout({ children }: { children: React.ReactNode }) { + return {children} +} diff --git a/apps/docs/app/guides/queues/[[...slug]]/page.tsx b/apps/docs/app/guides/queues/[[...slug]]/page.tsx new file mode 100644 index 00000000000..364dbb83475 --- /dev/null +++ b/apps/docs/app/guides/queues/[[...slug]]/page.tsx @@ -0,0 +1,25 @@ +import { + getGuidesMarkdown, + genGuideMeta, + genGuidesStaticParams, +} from '~/features/docs/GuidesMdx.utils' +import { GuideTemplate } from '~/features/docs/GuidesMdx.template' + +export const dynamicParams = false + +type Params = { slug?: string[] } + +const QueuesGuidePage = async ({ params }: { params: Params }) => { + const slug = ['queues', ...(params.slug ?? [])] + const data = await getGuidesMarkdown(slug) + + return +} + +const generateStaticParams = genGuidesStaticParams('queues') +const generateMetadata = genGuideMeta((params: { slug?: string[] }) => + getGuidesMarkdown(['queues', ...(params.slug ?? [])]) +) + +export default QueuesGuidePage +export { generateStaticParams, generateMetadata } diff --git a/apps/docs/app/guides/queues/layout.tsx b/apps/docs/app/guides/queues/layout.tsx new file mode 100644 index 00000000000..ac9d5c9042f --- /dev/null +++ b/apps/docs/app/guides/queues/layout.tsx @@ -0,0 +1,5 @@ +import Layout from '~/layouts/guides' + +export default async function QueuesLayout({ children }: { children: React.ReactNode }) { + return {children} +} diff --git a/apps/docs/app/guides/realtime/[[...slug]]/page.tsx b/apps/docs/app/guides/realtime/[[...slug]]/page.tsx new file mode 100644 index 00000000000..7a19de3cd7b --- /dev/null +++ b/apps/docs/app/guides/realtime/[[...slug]]/page.tsx @@ -0,0 +1,25 @@ +import { + getGuidesMarkdown, + genGuideMeta, + genGuidesStaticParams, +} from '~/features/docs/GuidesMdx.utils' +import { GuideTemplate } from '~/features/docs/GuidesMdx.template' + +export const dynamicParams = false + +type Params = { slug?: string[] } + +const RealtimeGuidePage = async ({ params }: { params: Params }) => { + const slug = ['realtime', ...(params.slug ?? [])] + const data = await getGuidesMarkdown(slug) + + return +} + +const generateStaticParams = genGuidesStaticParams('realtime') +const generateMetadata = genGuideMeta((params: { slug?: string[] }) => + getGuidesMarkdown(['realtime', ...(params.slug ?? [])]) +) + +export default RealtimeGuidePage +export { generateStaticParams, generateMetadata } diff --git a/apps/docs/app/guides/realtime/layout.tsx b/apps/docs/app/guides/realtime/layout.tsx new file mode 100644 index 00000000000..cb5d8dbada2 --- /dev/null +++ b/apps/docs/app/guides/realtime/layout.tsx @@ -0,0 +1,5 @@ +import Layout from '~/layouts/guides' + +export default async function RealtimeLayout({ children }: { children: React.ReactNode }) { + return {children} +} diff --git a/apps/docs/app/guides/resources/[[...slug]]/page.tsx b/apps/docs/app/guides/resources/[[...slug]]/page.tsx new file mode 100644 index 00000000000..ca23754cc5a --- /dev/null +++ b/apps/docs/app/guides/resources/[[...slug]]/page.tsx @@ -0,0 +1,25 @@ +import { + getGuidesMarkdown, + genGuideMeta, + genGuidesStaticParams, +} from '~/features/docs/GuidesMdx.utils' +import { GuideTemplate } from '~/features/docs/GuidesMdx.template' + +export const dynamicParams = false + +type Params = { slug?: string[] } + +const ResourcesGuidePage = async ({ params }: { params: Params }) => { + const slug = ['resources', ...(params.slug ?? [])] + const data = await getGuidesMarkdown(slug) + + return +} + +const generateStaticParams = genGuidesStaticParams('resources') +const generateMetadata = genGuideMeta((params: { slug?: string[] }) => + getGuidesMarkdown(['resources', ...(params.slug ?? [])]) +) + +export default ResourcesGuidePage +export { generateStaticParams, generateMetadata } diff --git a/apps/docs/app/guides/resources/layout.tsx b/apps/docs/app/guides/resources/layout.tsx new file mode 100644 index 00000000000..8b9d1ae4678 --- /dev/null +++ b/apps/docs/app/guides/resources/layout.tsx @@ -0,0 +1,5 @@ +import Layout from '~/layouts/guides' + +export default async function ResourcesLayout({ children }: { children: React.ReactNode }) { + return {children} +} diff --git a/apps/docs/app/guides/self-hosting/[[...slug]]/page.tsx b/apps/docs/app/guides/self-hosting/[[...slug]]/page.tsx new file mode 100644 index 00000000000..df826de3827 --- /dev/null +++ b/apps/docs/app/guides/self-hosting/[[...slug]]/page.tsx @@ -0,0 +1,25 @@ +import { + getGuidesMarkdown, + genGuideMeta, + genGuidesStaticParams, +} from '~/features/docs/GuidesMdx.utils' +import { GuideTemplate } from '~/features/docs/GuidesMdx.template' + +export const dynamicParams = false + +type Params = { slug?: string[] } + +const SelfHostingGuidePage = async ({ params }: { params: Params }) => { + const slug = ['self-hosting', ...(params.slug ?? [])] + const data = await getGuidesMarkdown(slug) + + return +} + +const generateStaticParams = genGuidesStaticParams('self-hosting') +const generateMetadata = genGuideMeta((params: { slug?: string[] }) => + getGuidesMarkdown(['self-hosting', ...(params.slug ?? [])]) +) + +export default SelfHostingGuidePage +export { generateStaticParams, generateMetadata } diff --git a/apps/docs/app/guides/(with-sidebar)/self-hosting/analytics/config/page.tsx b/apps/docs/app/guides/self-hosting/analytics/config/page.tsx similarity index 91% rename from apps/docs/app/guides/(with-sidebar)/self-hosting/analytics/config/page.tsx rename to apps/docs/app/guides/self-hosting/analytics/config/page.tsx index d397c0003f0..d7af68966fd 100644 --- a/apps/docs/app/guides/(with-sidebar)/self-hosting/analytics/config/page.tsx +++ b/apps/docs/app/guides/self-hosting/analytics/config/page.tsx @@ -2,7 +2,7 @@ import Param from '~/components/Params' import { genGuideMeta } from '~/features/docs/GuidesMdx.utils' import { GuideTemplate, newEditLink } from '~/features/docs/GuidesMdx.template' import { MDXRemoteBase } from '~/features/docs/MdxBase' -import specAnalyticsV0 from '~/spec/analytics_v0_config.yaml' assert { type: 'yml' } +import specAnalyticsV0 from '~/spec/analytics_v0_config.yaml' with { type: 'yml' } const meta = { title: 'Analytics Self-hosting Config', @@ -21,7 +21,7 @@ const AnalyticsConfigPage = async () => { diff --git a/apps/docs/app/guides/(with-sidebar)/self-hosting/auth/config/page.tsx b/apps/docs/app/guides/self-hosting/auth/config/page.tsx similarity index 91% rename from apps/docs/app/guides/(with-sidebar)/self-hosting/auth/config/page.tsx rename to apps/docs/app/guides/self-hosting/auth/config/page.tsx index d8a33d04694..4a0d480754e 100644 --- a/apps/docs/app/guides/(with-sidebar)/self-hosting/auth/config/page.tsx +++ b/apps/docs/app/guides/self-hosting/auth/config/page.tsx @@ -2,7 +2,7 @@ import Param from '~/components/Params' import { genGuideMeta } from '~/features/docs/GuidesMdx.utils' import { GuideTemplate, newEditLink } from '~/features/docs/GuidesMdx.template' import { MDXRemoteBase } from '~/features/docs/MdxBase' -import specAuthV1 from '~/spec/gotrue_v1_config.yaml' assert { type: 'yml' } +import specAuthV1 from '~/spec/gotrue_v1_config.yaml' with { type: 'yml' } const meta = { title: 'Auth Self-hosting Config', @@ -21,7 +21,7 @@ const AuthConfigPage = async () => { diff --git a/apps/docs/app/guides/self-hosting/layout.tsx b/apps/docs/app/guides/self-hosting/layout.tsx new file mode 100644 index 00000000000..14a59eb8dd1 --- /dev/null +++ b/apps/docs/app/guides/self-hosting/layout.tsx @@ -0,0 +1,5 @@ +import Layout from '~/layouts/guides' + +export default async function SelfHostingLayout({ children }: { children: React.ReactNode }) { + return {children} +} diff --git a/apps/docs/app/guides/(with-sidebar)/self-hosting/realtime/config/page.tsx b/apps/docs/app/guides/self-hosting/realtime/config/page.tsx similarity index 90% rename from apps/docs/app/guides/(with-sidebar)/self-hosting/realtime/config/page.tsx rename to apps/docs/app/guides/self-hosting/realtime/config/page.tsx index 01180ba859c..80d76288cd5 100644 --- a/apps/docs/app/guides/(with-sidebar)/self-hosting/realtime/config/page.tsx +++ b/apps/docs/app/guides/self-hosting/realtime/config/page.tsx @@ -2,7 +2,7 @@ import Param from '~/components/Params' import { genGuideMeta } from '~/features/docs/GuidesMdx.utils' import { GuideTemplate, newEditLink } from '~/features/docs/GuidesMdx.template' import { MDXRemoteBase } from '~/features/docs/MdxBase' -import specRealtimeV0 from '~/spec/realtime_v0_config.yaml' assert { type: 'yml' } +import specRealtimeV0 from '~/spec/realtime_v0_config.yaml' with { type: 'yml' } const meta = { title: 'Realtime Self-hosting Config', @@ -21,7 +21,7 @@ const RealtimeConfigPage = async () => { diff --git a/apps/docs/app/guides/(with-sidebar)/self-hosting/storage/config/page.tsx b/apps/docs/app/guides/self-hosting/storage/config/page.tsx similarity index 91% rename from apps/docs/app/guides/(with-sidebar)/self-hosting/storage/config/page.tsx rename to apps/docs/app/guides/self-hosting/storage/config/page.tsx index 766bc4d190a..a53adae5ec2 100644 --- a/apps/docs/app/guides/(with-sidebar)/self-hosting/storage/config/page.tsx +++ b/apps/docs/app/guides/self-hosting/storage/config/page.tsx @@ -2,7 +2,7 @@ import Param from '~/components/Params' import { genGuideMeta } from '~/features/docs/GuidesMdx.utils' import { GuideTemplate, newEditLink } from '~/features/docs/GuidesMdx.template' import { MDXRemoteBase } from '~/features/docs/MdxBase' -import specStorageV0 from '~/spec/storage_v0_config.yaml' assert { type: 'yml' } +import specStorageV0 from '~/spec/storage_v0_config.yaml' with { type: 'yml' } const meta = { title: 'Storage Self-hosting Config', @@ -21,7 +21,7 @@ const StorageConfigPage = async () => { diff --git a/apps/docs/app/guides/storage/[[...slug]]/page.tsx b/apps/docs/app/guides/storage/[[...slug]]/page.tsx new file mode 100644 index 00000000000..bf1754495ac --- /dev/null +++ b/apps/docs/app/guides/storage/[[...slug]]/page.tsx @@ -0,0 +1,25 @@ +import { + getGuidesMarkdown, + genGuideMeta, + genGuidesStaticParams, +} from '~/features/docs/GuidesMdx.utils' +import { GuideTemplate } from '~/features/docs/GuidesMdx.template' + +export const dynamicParams = false + +type Params = { slug?: string[] } + +const StorageGuidePage = async ({ params }: { params: Params }) => { + const slug = ['storage', ...(params.slug ?? [])] + const data = await getGuidesMarkdown(slug) + + return +} + +const generateStaticParams = genGuidesStaticParams('storage') +const generateMetadata = genGuideMeta((params: { slug?: string[] }) => + getGuidesMarkdown(['storage', ...(params.slug ?? [])]) +) + +export default StorageGuidePage +export { generateStaticParams, generateMetadata } diff --git a/apps/docs/app/guides/storage/layout.tsx b/apps/docs/app/guides/storage/layout.tsx new file mode 100644 index 00000000000..0a59848fbeb --- /dev/null +++ b/apps/docs/app/guides/storage/layout.tsx @@ -0,0 +1,5 @@ +import Layout from '~/layouts/guides' + +export default async function StorageLayout({ children }: { children: React.ReactNode }) { + return {children} +} diff --git a/apps/docs/app/guides/telemetry/[[...slug]]/page.tsx b/apps/docs/app/guides/telemetry/[[...slug]]/page.tsx new file mode 100644 index 00000000000..3ad5e6c392e --- /dev/null +++ b/apps/docs/app/guides/telemetry/[[...slug]]/page.tsx @@ -0,0 +1,25 @@ +import { + getGuidesMarkdown, + genGuideMeta, + genGuidesStaticParams, +} from '~/features/docs/GuidesMdx.utils' +import { GuideTemplate } from '~/features/docs/GuidesMdx.template' + +export const dynamicParams = false + +type Params = { slug?: string[] } + +const MonitoringTroubleshootingGuidePage = async ({ params }: { params: Params }) => { + const slug = ['telemetry', ...(params.slug ?? [])] + const data = await getGuidesMarkdown(slug) + + return +} + +const generateStaticParams = genGuidesStaticParams('telemetry') +const generateMetadata = genGuideMeta((params: { slug?: string[] }) => + getGuidesMarkdown(['telemetry', ...(params.slug ?? [])]) +) + +export default MonitoringTroubleshootingGuidePage +export { generateStaticParams, generateMetadata } diff --git a/apps/docs/app/guides/telemetry/layout.tsx b/apps/docs/app/guides/telemetry/layout.tsx new file mode 100644 index 00000000000..e2fe19972d4 --- /dev/null +++ b/apps/docs/app/guides/telemetry/layout.tsx @@ -0,0 +1,5 @@ +import Layout from '~/layouts/guides' + +export default async function Telemetry({ children }: { children: React.ReactNode }) { + return {children} +} diff --git a/apps/docs/app/guides/troubleshooting/[slug]/page.tsx b/apps/docs/app/guides/troubleshooting/[slug]/page.tsx index 428a2e84adc..9fcd77530d1 100644 --- a/apps/docs/app/guides/troubleshooting/[slug]/page.tsx +++ b/apps/docs/app/guides/troubleshooting/[slug]/page.tsx @@ -4,13 +4,17 @@ import TroubleshootingPage from '~/features/docs/Troubleshooting.page' import { getAllTroubleshootingEntries, getArticleSlug } from '~/features/docs/Troubleshooting.utils' import { PROD_URL } from '~/lib/constants' +// 60 seconds/minute * 60 minutes/hour * 24 hours/day +// export const revalidate = 86_400 +export const dynamicParams = false + export default async function TroubleshootingEntryPage({ params: { slug }, }: { params: { slug: string } }) { const allTroubleshootingEntries = await getAllTroubleshootingEntries() - const entry = allTroubleshootingEntries.find((entry) => getArticleSlug(entry.data) === slug) + const entry = allTroubleshootingEntries.find((entry) => getArticleSlug(entry) === slug) if (!entry) { notFound() @@ -21,7 +25,7 @@ export default async function TroubleshootingEntryPage({ export const generateMetadata = async ({ params: { slug } }: { params: { slug: string } }) => { const allTroubleshootingEntries = await getAllTroubleshootingEntries() - const entry = allTroubleshootingEntries.find((entry) => getArticleSlug(entry.data) === slug) + const entry = allTroubleshootingEntries.find((entry) => getArticleSlug(entry) === slug) return { title: 'Supabase Docs | Troubleshooting' + (entry ? ` | ${entry.data.title}` : ''), @@ -33,5 +37,5 @@ export const generateMetadata = async ({ params: { slug } }: { params: { slug: s export const generateStaticParams = async () => { const allTroubleshootingEntries = await getAllTroubleshootingEntries() - return allTroubleshootingEntries.map((entry) => ({ slug: getArticleSlug(entry.data) })) + return allTroubleshootingEntries.map((entry) => ({ slug: getArticleSlug(entry) })) } diff --git a/apps/docs/app/guides/troubleshooting/page.tsx b/apps/docs/app/guides/troubleshooting/page.tsx index e5e96fe66df..1a519cf26b0 100644 --- a/apps/docs/app/guides/troubleshooting/page.tsx +++ b/apps/docs/app/guides/troubleshooting/page.tsx @@ -16,6 +16,9 @@ import { TROUBLESHOOTING_CONTAINER_ID } from '~/features/docs/Troubleshooting.ut import { SidebarSkeleton } from '~/layouts/MainSkeleton' import { PROD_URL } from '~/lib/constants' +// 60 seconds/minute * 60 minutes/hour * 24 hours/day +// export const revalidate = 86_400 + export default async function GlobalTroubleshootingPage() { const troubleshootingEntries = await getAllTroubleshootingEntries() const keywords = await getAllTroubleshootingKeywords() @@ -23,7 +26,7 @@ export default async function GlobalTroubleshootingPage() { const errors = await getAllTroubleshootingErrors() return ( - +

Troubleshooting

@@ -38,11 +41,14 @@ export default async function GlobalTroubleshootingPage() { /> -

+

Matching troubleshooting entries

-
    +
      {troubleshootingEntries.map((entry) => ( -
    • +
    • ))} diff --git a/apps/docs/app/layout.tsx b/apps/docs/app/layout.tsx index 08bcedfdcc3..1074889fa23 100644 --- a/apps/docs/app/layout.tsx +++ b/apps/docs/app/layout.tsx @@ -4,12 +4,13 @@ import '../styles/main.scss' import '../styles/new-docs.scss' import '../styles/prism-okaidia.scss' -import { genFaviconData } from 'common/MetaFavicons/app-router' import { type Metadata, type Viewport } from 'next' -import { BASE_PATH, IS_PRODUCTION } from '~/lib/constants' +import { genFaviconData } from 'common/MetaFavicons/app-router' + import { GlobalProviders } from '~/features/app.providers' import { TopNavSkeleton } from '~/layouts/MainSkeleton' +import { BASE_PATH, IS_PRODUCTION } from '~/lib/constants' const metadata: Metadata = { applicationName: 'Supabase Docs', diff --git a/apps/docs/app/page.tsx b/apps/docs/app/page.tsx index ad7a21f787b..9286d1a2f20 100644 --- a/apps/docs/app/page.tsx +++ b/apps/docs/app/page.tsx @@ -1,9 +1,10 @@ import { type Metadata, type ResolvingMetadata } from 'next' import Link from 'next/link' -import { IconBackground, TextLink } from 'ui' +import { cn, IconBackground, TextLink } from 'ui' import { IconPanel } from 'ui-patterns/IconPanel' import MenuIconPicker from '~/components/Navigation/NavigationMenu/MenuIconPicker' +import { MIGRATION_PAGES } from '~/components/Navigation/NavigationMenu/NavigationMenu.constants' import { GlassPanelWithIconPicker } from '~/features/ui/GlassPanelWithIconPicker' import { IconPanelWithIconPicker } from '~/features/ui/IconPanelWithIconPicker' import HomeLayout from '~/layouts/HomeLayout' @@ -28,6 +29,7 @@ const products = [ href: '/guides/database/overview', description: 'Supabase provides a full Postgres database for every project with Realtime functionality, database backups, extensions, and more.', + span: 'col-span-12 md:col-span-6', }, { title: 'Auth', @@ -36,6 +38,7 @@ const products = [ href: '/guides/auth', description: 'Add and manage email and password, passwordless, OAuth, and mobile logins to your project through a suite of identity providers and APIs.', + span: 'col-span-12 md:col-span-6', }, { title: 'Storage', @@ -45,13 +48,6 @@ const products = [ description: 'Store, organize, transform, and serve large files—fully integrated with your Postgres database with Row Level Security access policies.', }, - { - title: 'AI & Vectors', - icon: 'ai', - hasLightIcon: true, - href: '/guides/ai', - description: 'Use Supabase to store and search embedding vectors.', - }, { title: 'Realtime', icon: 'realtime', @@ -70,57 +66,24 @@ const products = [ }, ] -const migrationGuides = [ +const postgresIntegrations = [ { - title: 'Auth0', - icon: '/docs/img/icons/auth0-icon', - href: '/guides/resources/migrating-to-supabase/auth0', - hasLightIcon: true, + title: 'AI & Vectors', + icon: 'ai', + href: '/guides/ai', + description: 'AI toolkit to manage embeddings', }, { - title: 'Firebase Auth', - icon: '/docs/img/icons/firebase-icon', - href: '/guides/resources/migrating-to-supabase/firebase-auth', + title: 'Cron', + icon: 'cron', + href: '/guides/cron', + description: 'Schedule and monitor recurring Jobs', }, { - title: 'Firestore Data', - icon: '/docs/img/icons/firebase-icon', - href: '/guides/resources/migrating-to-supabase/firestore-data', - }, - { - title: 'Firebase Storage', - icon: '/docs/img/icons/firebase-icon', - href: '/guides/resources/migrating-to-supabase/firebase-storage', - }, - { - title: 'Heroku', - icon: '/docs/img/icons/heroku-icon', - href: '/guides/resources/migrating-to-supabase/heroku', - }, - { - title: 'Render', - icon: '/docs/img/icons/render-icon', - href: '/guides/resources/migrating-to-supabase/render', - }, - { - title: 'Amazon RDS', - icon: '/docs/img/icons/aws-rds-icon', - href: '/guides/resources/migrating-to-supabase/amazon-rds', - }, - { - title: 'Postgres', - icon: '/docs/img/icons/postgres-icon', - href: '/guides/resources/migrating-to-supabase/postgres', - }, - { - title: 'MySQL', - icon: '/docs/img/icons/mysql-icon', - href: '/guides/resources/migrating-to-supabase/mysql', - }, - { - title: 'MSSQL', - icon: '/docs/img/icons/mssql-icon', - href: '/guides/resources/migrating-to-supabase/mssql', + title: 'Queues', + icon: 'queues', + href: '/guides/queues', + description: 'Durable Message Queues with guaranteed delivery', }, ] @@ -214,7 +177,7 @@ const HomePage = () => (
        {products.map((product) => { return ( -
      • +
      • {product.description} @@ -225,6 +188,26 @@ const HomePage = () => ( })}
      +
      +
      +

      + Postgres Modules +

      +
      +
      + {postgresIntegrations.map((integration) => ( + + + + ))} +
      +
      +
      @@ -268,11 +251,11 @@ const HomePage = () => (
        - {migrationGuides.map((guide) => { + {MIGRATION_PAGES.sort((a, b) => a.name.localeCompare(b.name)).map((guide) => { return ( -
      • - - +
      • + +
      • ) diff --git a/apps/docs/app/reference/[...slug]/page.tsx b/apps/docs/app/reference/[...slug]/page.tsx index 5bc1ec9fa27..c15880c4e06 100644 --- a/apps/docs/app/reference/[...slug]/page.tsx +++ b/apps/docs/app/reference/[...slug]/page.tsx @@ -12,11 +12,14 @@ import { redirectNonexistentReferenceSection, } from '~/features/docs/Reference.utils' +export const dynamicParams = false + export default async function ReferencePage({ params: { slug }, }: { params: { slug: Array } }) { + console.log("Generating reference page for '%o'", slug) if (!Object.keys(REFERENCES).includes(slug[0].replaceAll('-', '_'))) { notFound() } diff --git a/apps/docs/components/AuthSmsProviderConfig/AuthSmsProviderConfig.Warnings.tsx b/apps/docs/components/AuthSmsProviderConfig/AuthSmsProviderConfig.Warnings.tsx index 4043d3432e6..d1bb35b32f5 100644 --- a/apps/docs/components/AuthSmsProviderConfig/AuthSmsProviderConfig.Warnings.tsx +++ b/apps/docs/components/AuthSmsProviderConfig/AuthSmsProviderConfig.Warnings.tsx @@ -5,12 +5,20 @@ const CostWarning = () => (

        To keep SMS sending costs under control, make sure you adjust your project's rate limits - and configure CAPTCHA. See the{' '} - Production Checklist to learn more. + and{' '} + + configure CAPTCHA + + . See the{' '} + + Production Checklist + {' '} + to learn more.

        +

        - Some countries have special regulations for services that send SMS messages to users, for - example, India's TRAI DLT regulations. Remember to look up and follow the regulations of + Some countries have special regulations for services that send SMS messages to users, (e.g + India's TRAI DLT regulations). Remember to look up and follow the regulations of countries where you operate.

        diff --git a/apps/docs/components/Breadcrumbs.tsx b/apps/docs/components/Breadcrumbs.tsx index 398a5112c2a..d41b2b0e941 100644 --- a/apps/docs/components/Breadcrumbs.tsx +++ b/apps/docs/components/Breadcrumbs.tsx @@ -109,15 +109,15 @@ const BreadcrumbsInternal = ({
        - {breadcrumbs - .slice(1, -2) - .map((crumb) => - crumb.url ? ( - {crumb.title || crumb.name} - ) : ( - crumb.title || crumb.name - ) - )} + {breadcrumbs.slice(1, -2).map((crumb, index) => + crumb.url ? ( + + {crumb.title || crumb.name} + + ) : ( + crumb.title || crumb.name + ) + )}
        @@ -131,12 +131,12 @@ const BreadcrumbsInternal = ({ )} - {appendedBreadcrumbs?.map((crumb, i) => ( - + {appendedBreadcrumbs?.map((crumb, index) => ( + {crumb.url ? ( @@ -148,7 +148,7 @@ const BreadcrumbsInternal = ({ )} ))} @@ -166,6 +166,16 @@ function useBreadcrumbs() { return breadcrumbs } + const isAiPromptsPage = pathname.startsWith('/guides/getting-started/ai-prompts') + if (isAiPromptsPage) { + const breadcrumbs = [ + { name: 'Getting started', url: '/guides/getting-started' }, + { name: 'AI Tools' }, + { name: 'Prompts', url: '/guides/getting-started/ai-prompts' }, + ] + return breadcrumbs + } + const menuId = getMenuId(pathname) const menu = NavItems[menuId] return findMenuItemByUrl(menu, pathname, []) diff --git a/apps/docs/components/Feedback/Feedback.tsx b/apps/docs/components/Feedback/Feedback.tsx index a5870ad675d..f16063a9209 100644 --- a/apps/docs/components/Feedback/Feedback.tsx +++ b/apps/docs/components/Feedback/Feedback.tsx @@ -1,6 +1,6 @@ 'use client' -import { useSupabaseClient } from '@supabase/auth-helpers-react' +import { createClient } from '@supabase/supabase-js' import { Check, MessageSquareQuote, X } from 'lucide-react' import { usePathname } from 'next/navigation' import { @@ -12,13 +12,15 @@ import { useState, } from 'react' -import { type Database, useIsLoggedIn } from 'common' +import { type Database, useConstant, useIsLoggedIn } from 'common' +import { TelemetryActions } from 'common/telemetry-constants' import { Button, cn } from 'ui' +import { IS_PLATFORM } from '~/lib/constants' import { useSendFeedbackMutation } from '~/lib/fetch/feedback' import { useSendTelemetryEvent } from '~/lib/telemetry' -import { FeedbackModal, type FeedbackFields } from './FeedbackModal' import { getNotionTeam, getSanitizedTabParams } from './Feedback.utils' +import { type FeedbackFields, FeedbackModal } from './FeedbackModal' const FeedbackButton = forwardRef< HTMLButtonElement, @@ -79,7 +81,14 @@ function Feedback({ className }: { className?: string }) { const pathname = usePathname() ?? '' const sendTelemetryEvent = useSendTelemetryEvent() const { mutate: sendFeedbackComment } = useSendFeedbackMutation() - const supabase = useSupabaseClient() + const supabase = useConstant(() => + IS_PLATFORM + ? createClient( + process.env.NEXT_PUBLIC_SUPABASE_URL!, + process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY! + ) + : undefined + ) const unanswered = state.type === 'unanswered' const isYes = 'response' in state && state.response === 'yes' @@ -100,9 +109,8 @@ function Feedback({ className }: { className?: string }) { function handleVote(response: Response) { sendTelemetryEvent({ - category: 'docs', - action: 'feedback_voted', - label: response, + action: TelemetryActions.DOCS_FEEDBACK_CLICKED, + properties: { response }, }) sendFeedbackVote(response) dispatch({ event: 'VOTED', response }) diff --git a/apps/docs/components/Feedback/Feedback.utils.ts b/apps/docs/components/Feedback/Feedback.utils.ts index c1e93f10f27..cf9ff4c989a 100644 --- a/apps/docs/components/Feedback/Feedback.utils.ts +++ b/apps/docs/components/Feedback/Feedback.utils.ts @@ -24,11 +24,15 @@ const getNotionTeam = (pathname: string) => { return 'team-realtime' case 'ai': return 'team-ai' - case 'cli': - return 'team-cli' - - // Ignoring platform for now because that section is a mix of teams. + case 'local-development': + case 'self-hosting': + case 'deployment': + return 'team-dev-workflows' + case 'integrations': + return 'team-api' case 'platform': + case 'monitoring-troubleshooting': + return 'team-infra' default: return DEFAULT_TEAM } diff --git a/apps/docs/components/GuidesTableOfContents.tsx b/apps/docs/components/GuidesTableOfContents.tsx index af1b92852ec..32ad52df066 100644 --- a/apps/docs/components/GuidesTableOfContents.tsx +++ b/apps/docs/components/GuidesTableOfContents.tsx @@ -1,7 +1,7 @@ 'use client' import { usePathname } from 'next/navigation' -import { useEffect, useState } from 'react' +import { Fragment, useEffect, useState } from 'react' import { cn } from 'ui' import { ExpandableVideo } from 'ui-patterns/ExpandableVideo' import { proxy, useSnapshot } from 'valtio' @@ -20,23 +20,32 @@ const formatSlug = (slug: string) => { return slug } -const formatTOCHeader = (content: string) => { - let begin = false - const res: Array = [] +function formatTOCHeader(content: string) { + let insideInlineCode = false + const res: Array<{ type: 'text'; value: string } | { type: 'code'; value: string }> = [] + for (const x of content) { if (x === '`') { - if (!begin) { - begin = true - res.push(``) + if (!insideInlineCode) { + insideInlineCode = true + res.push({ type: 'code', value: '' }) } else { - begin = false - res.push(``) + insideInlineCode = false } } else { - res.push(x) + if (insideInlineCode) { + res[res.length - 1].value += x + } else { + if (res.length === 0 || res[res.length - 1].type === 'code') { + res.push({ type: 'text', value: x }) + } else { + res[res.length - 1].value += x + } + } } } - return res.join('') + + return res } const tocRenderSwitch = proxy({ @@ -119,8 +128,6 @@ const GuidesTableOfContents = ({ // eslint-disable-next-line react-hooks/exhaustive-deps }, [hash, JSON.stringify(displayedList)]) - if (!displayedList.length) return - const tocVideoPreview = `https://img.youtube.com/vi/${video}/0.jpg` return ( @@ -131,20 +138,33 @@ const GuidesTableOfContents = ({
      )} - - On this page - - + {displayedList.length > 0 && ( + <> + + On this page + + + + )}
      ) } diff --git a/apps/docs/components/HomePageCover.tsx b/apps/docs/components/HomePageCover.tsx index 4e8bff25734..25bd3b32a48 100644 --- a/apps/docs/components/HomePageCover.tsx +++ b/apps/docs/components/HomePageCover.tsx @@ -1,11 +1,32 @@ 'use client' -import { useBreakpoint } from 'common' +import { ChevronRight, Play, Sparkles } from 'lucide-react' import Link from 'next/link' -import { IconBackground } from 'ui' + +import { useBreakpoint } from 'common' +import { cn, IconBackground } from 'ui' import { IconPanel } from 'ui-patterns/IconPanel' + import DocsCoverLogo from './DocsCoverLogo' -import { Play } from 'lucide-react' + +function AiPrompt({ className }: { className?: string }) { + return ( + + + Start with Supabase AI prompts + + + ) +} const HomePageCover = (props) => { const isXs = useBreakpoint(639) @@ -53,7 +74,7 @@ const HomePageCover = (props) => { href: '/guides/getting-started/quickstarts/vue', }, { - tooltip: 'NuxtJS', + tooltip: 'Nuxt', icon: '/docs/img/icons/nuxt-icon', href: '/guides/getting-started/quickstarts/nuxtjs', }, @@ -74,7 +95,7 @@ const HomePageCover = (props) => { p-5 md:p-8 " > -
      +
      @@ -83,20 +104,23 @@ const HomePageCover = (props) => {

      Getting Started

      - Discover how to set up a database to an app making queries in just a few minutes. + Set up and connect a database in just a few minutes.

      -
      - {frameworks.map((framework, i) => ( - - - - ))} +
      +
      + {frameworks.map((framework, i) => ( + + + + ))} +
      +
      diff --git a/apps/docs/components/JwtGenerator/JwtGenerator.tsx b/apps/docs/components/JwtGenerator/JwtGenerator.tsx index fceaab1c44d..189c466f0dd 100644 --- a/apps/docs/components/JwtGenerator/JwtGenerator.tsx +++ b/apps/docs/components/JwtGenerator/JwtGenerator.tsx @@ -75,7 +75,7 @@ export default function JwtGenerator({}) { } return ( -
      +
      - + - - - - + + + + ``` ```svelte src/routes/auth/+layout.svelte + +
      - +
      - + +{@render children()} ``` ```svelte src/routes/auth/error/+page.svelte @@ -512,25 +518,25 @@ To ensure that `hooks.server.ts` runs for every nested path, put a `+layout.serv ```svelte src/routes/private/+layout.svelte
      - - + +
      - + {@render children()}
      ``` @@ -561,55 +567,54 @@ using ((select auth.uid()) = user_id); ``` ```svelte src/routes/private/+page.server.ts -import type { PageServerLoad } from './$types'; +import type { PageServerLoad } from './$types' export const load: PageServerLoad = async ({ depends, locals: { supabase } }) => { - depends('supabase:db:notes'); - const { data: notes } = await supabase.from('notes').select('id,note').order('id'); - return { notes: notes ?? [] }; -}; + depends('supabase:db:notes') + const { data: notes } = await supabase.from('notes').select('id,note').order('id') + return { notes: notes ?? [] } +} ``` ```svelte src/routes/private/+page.svelte

      Private page for user: {user?.email}

      Notes

        - {#each notes as note} -
      • {note.note}
      • - {/each} + {#each notes as note} +
      • {note.note}
      • + {/each}
      -
      - + +
      ``` diff --git a/apps/docs/content/guides/auth/sessions.mdx b/apps/docs/content/guides/auth/sessions.mdx index 7d71f848912..3a219162e6a 100644 --- a/apps/docs/content/guides/auth/sessions.mdx +++ b/apps/docs/content/guides/auth/sessions.mdx @@ -119,7 +119,7 @@ When a user signs out, the sessions affected by the logout are removed from the Note that sessions are not proactively terminated when their maximum lifetime (time-box) or inactivity timeout are reached. These sessions are cleaned up progressively 24 hours after reaching that status. This allows you to tweak the values or roll back changes without causing unintended user friction. -### Can I use HTTP-Only cookies to store the access and refresh tokens? +### Can I use HTTP-only cookies to store the access and refresh tokens? This is possible, but only for apps that use the traditional server-only web app approach where all of the application logic is implemented on the server and it returns rendered HTML only. diff --git a/apps/docs/content/guides/auth/social-login/auth-apple.mdx b/apps/docs/content/guides/auth/social-login/auth-apple.mdx index df5ba7c745b..2d9223f0010 100644 --- a/apps/docs/content/guides/auth/social-login/auth-apple.mdx +++ b/apps/docs/content/guides/auth/social-login/auth-apple.mdx @@ -63,7 +63,7 @@ When developing with Expo, you can test Sign in with Apple via the Expo Go app, 6. Create a signing **Key** in the [Keys](https://developer.apple.com/account/resources/authkeys/list) section of the Apple Developer Console. You can use this key to generate a secret key using the tool below, which is added to your Supabase project's Auth configuration. Make sure you safely store the `AuthKey_XXXXXXXXXX.p8` file. If you ever lose access to it, or make it public accidentally please revoke it from the Apple Developer Console and create a new one immediately. You will have to generate a new secret key using this file every 6 months, so make sure you schedule a recurring meeting in your calendar! 7. Finally, add the information you configured above to the [Apple provider configuration in the Supabase dashboard](https://supabase.com/dashboard/project/_/auth/providers). - + Use this tool to generate a new Apple client secret. No keys leave your browser! Be aware that this tool does not currently work in Safari, so please use Firefox or a Chrome-based browser instead. @@ -111,7 +111,7 @@ When developing with Expo, you can test Sign in with Apple via the Expo Go app, 1. Have an **App ID** which uniquely identifies the app you are building. You can create a new App ID from the [Identifiers](https://developer.apple.com/account/resources/identifiers/list/bundleId) section in the Apple Developer Console (use the filter menu in the upper right side to see all App IDs). These usually are a reverse domain name string, for example `com.example.app`. Make sure you configure Sign in with Apple for the App ID you created or already have, in the Capabilities list. At this time Supabase Auth does not support Server-to-Server notification endpoints, so you should leave that setting blank. (In the past App IDs were referred to as _bundle IDs._) 2. Obtain a **Services ID** attached to the App ID that uniquely identifies the website. Use this value as the client ID when initializing Sign in with Apple JS. You can create a new Services ID from the [Identifiers](https://developer.apple.com/account/resources/identifiers/list/serviceId) section in the Apple Developer Console (use the filter menu in the upper right side to see all Services IDs). These usually are a reverse domain name string, for example `com.example.app.website`. 3. Configure Website URLs for the newly created **Services ID**. The web domain you should use is the domain your website is hosted on. The redirect URL must also point to a page on your website that will receive the callback from Apple. - 4. Register the Services ID you created to your project's [Apple provider configuration in the Supabase dashboard](https://supabase.com/dashboard/project/_/auth/providers) under _Authorized Client IDs_. + 4. Register the Services ID you created to your project's [Apple provider configuration in the Supabase dashboard](https://supabase.com/dashboard/project/_/auth/providers) under _Client IDs_. If you're using Sign in with Apple JS you do not need to configure the OAuth settings. @@ -184,12 +184,12 @@ When developing with Expo, you can test Sign in with Apple via the Expo Go app, - When testing with Expo Go, the Expo App ID `host.exp.Exponent` will be used. Make sure to add this to the "Authorized Client IDs" list in your [Supabase dashboard Apple provider configuration](https://supabase.com/dashboard/project/_/auth/providers)! + When testing with Expo Go, the Expo App ID `host.exp.Exponent` will be used. Make sure to add this to the "Client IDs" list in your [Supabase dashboard Apple provider configuration](https://supabase.com/dashboard/project/_/auth/providers)! 1. Have an **App ID** which uniquely identifies the app you are building. You can create a new App ID from the [Identifiers](https://developer.apple.com/account/resources/identifiers/list/bundleId) section in the Apple Developer Console (use the filter menu in the upper right side to see all App IDs). These usually are a reverse domain name string, for example `com.example.app`. Make sure you configure Sign in with Apple for the App ID you created or already have, in the Capabilities list. At this time Supabase Auth does not support Server-to-Server notification endpoints, so you should leave that setting blank. (In the past App IDs were referred to as _bundle IDs._) - 2. Register all of the App IDs that will be using your Supabase project in the [Apple provider configuration in the Supabase dashboard](https://supabase.com/dashboard/project/_/auth/providers) under _Authorized Client IDs_. + 2. Register all of the App IDs that will be using your Supabase project in the [Apple provider configuration in the Supabase dashboard](https://supabase.com/dashboard/project/_/auth/providers) under _Client IDs_. @@ -243,7 +243,7 @@ When developing with Expo, you can test Sign in with Apple via the Expo Go app, ### Configuration [#flutter-configuration-native-app] 1. Have an **App ID** which uniquely identifies the app you are building. You can create a new App ID from the [Identifiers](https://developer.apple.com/account/resources/identifiers/list/bundleId) section in the Apple Developer Console (use the filter menu in the upper right side to see all App IDs). These usually are a reverse domain name string, for example `com.example.app`. Make sure you configure Sign in with Apple for the App ID you created or already have, in the Capabilities list. At this time Supabase Auth does not support Server-to-Server notification endpoints, so you should leave that setting blank. (In the past App IDs were referred to as _bundle IDs._) - 2. Register all of the App IDs that will be using your Supabase project in the [Apple provider configuration in the Supabase dashboard](https://supabase.com/dashboard/project/_/auth/providers) under _Authorized Client IDs_. + 2. Register all of the App IDs that will be using your Supabase project in the [Apple provider configuration in the Supabase dashboard](https://supabase.com/dashboard/project/_/auth/providers) under _Client IDs_. ## Apple sign in on Android, Web, Windows and Linux @@ -280,7 +280,7 @@ When developing with Expo, you can test Sign in with Apple via the Expo Go app, 6. Create a signing **Key** in the [Keys](https://developer.apple.com/account/resources/authkeys/list) section of the Apple Developer Console. You can use this key to generate a secret key using the tool below, which is added to your Supabase project's Auth configuration. Make sure you safely store the `AuthKey_XXXXXXXXXX.p8` file. If you ever lose access to it, or make it public accidentally please revoke it from the Apple Developer Console and create a new one immediately. You will have to generate a new secret key using this file every 6 months, so make sure you schedule a recurring reminder in your calendar! 7. Finally, add the information you configured above to the [Apple provider configuration in the Supabase dashboard](https://supabase.com/dashboard/project/_/auth/providers). - + Use this tool to generate a new Apple client secret. No keys leave your browser! Be aware that this tool does not currently work in Safari, so please use Firefox or a Chrome-based browser instead. @@ -339,7 +339,7 @@ When developing with Expo, you can test Sign in with Apple via the Expo Go app, ### Configuration [#swift-configuration-native-app] 1. Have an **App ID** which uniquely identifies the app you are building. You can create a new App ID from the [Identifiers](https://developer.apple.com/account/resources/identifiers/list/bundleId) section in the Apple Developer Console (use the filter menu in the upper right side to see all App IDs). These usually are a reverse domain name string, for example `com.example.app`. Make sure you configure Sign in with Apple for the App ID you created or already have, in the Capabilities list. At this time Supabase Auth does not support Server-to-Server notification endpoints, so you should leave that setting blank. (In the past App IDs were referred to as _bundle IDs._) - 2. Register all of the App IDs that will be using your Supabase project in the [Apple provider configuration in the Supabase dashboard](https://supabase.com/dashboard/project/_/auth/providers) under _Authorized Client IDs_. + 2. Register all of the App IDs that will be using your Supabase project in the [Apple provider configuration in the Supabase dashboard](https://supabase.com/dashboard/project/_/auth/providers) under _Client IDs_. @@ -348,12 +348,12 @@ When developing with Expo, you can test Sign in with Apple via the Expo Go app, - + - + ## Using native sign in with Apple in Kotlin - - When using [Compose Multiplatform](https://github.com/JetBrains/compose-multiplatform/), you can use the [compose-auth](https://supabase.com/docs/reference/kotlin/installing) plugin. On iOS it uses Native Apple Login automatically and on other platforms it uses `gotrue.signInWith(Apple)`. + + When using [Compose Multiplatform](https://github.com/JetBrains/compose-multiplatform/), you can use the [compose-auth](https://supabase.com/docs/reference/kotlin/installing) plugin. On iOS it uses Native Apple Login automatically and on other platforms it uses `gotrue.signInWith(Apple)`. **Initialize the Supabase Client** diff --git a/apps/docs/content/guides/auth/social-login/auth-azure.mdx b/apps/docs/content/guides/auth/social-login/auth-azure.mdx index 47f6b95dfb6..f31c269492a 100644 --- a/apps/docs/content/guides/auth/social-login/auth-azure.mdx +++ b/apps/docs/content/guides/auth/social-login/auth-azure.mdx @@ -24,7 +24,7 @@ Setting up OAuth with Azure consists of four broad steps: - Under Microsoft Entra ID, select _App registrations_ in the side panel and select _New registration._ - Choose a name and select your preferred option for the supported account types. -- Specify a _Web_ _Redirect URI_. It should should look like this: `https://.supabase.co/auth/v1/callback` +- Specify a _Web_ _Redirect URI_. It should look like this: `https://.supabase.co/auth/v1/callback` - Finally, select _Register_ at the bottom of the screen. ![Register an application.](/docs/img/guides/auth-azure/azure-register-app.png) @@ -126,7 +126,7 @@ Supabase Auth requires that Azure returns a valid email address. Therefore you m -When your user signs in, call [signInWithOAuth()](/docs/reference/javascript/auth-signinwithoauth) with `azure` as the `provider`: +When your user signs in, call [`signInWithOAuth()`](/docs/reference/javascript/auth-signinwithoauth) with `azure` as the `provider`: ```js async function signInWithAzure() { @@ -142,7 +142,7 @@ async function signInWithAzure() { -When your user signs in, call [signInWithOAuth()](/docs/reference/dart/auth-signinwithoauth) with `azure` as the `provider`: +When your user signs in, call [`signInWithOAuth()`](/docs/reference/dart/auth-signinwithoauth) with `azure` as the `provider`: ```dart Future signInWithAzure() async { diff --git a/apps/docs/content/guides/auth/social-login/auth-bitbucket.mdx b/apps/docs/content/guides/auth/social-login/auth-bitbucket.mdx index 7c3d394a00c..a690d3cf9aa 100644 --- a/apps/docs/content/guides/auth/social-login/auth-bitbucket.mdx +++ b/apps/docs/content/guides/auth/social-login/auth-bitbucket.mdx @@ -4,7 +4,7 @@ title: 'Login with Bitbucket' description: 'Add Bitbucket OAuth to your Supabase project' --- -To enable Bitbucket Auth for your project, you need to set up a BitBucket OAuth application and add the application credentials to your Supabase Dashboard. +To enable Bitbucket Auth for your project, you need to set up a Bitbucket OAuth application and add the application credentials to your Supabase Dashboard. ## Overview @@ -57,7 +57,7 @@ Setting up Bitbucket logins for your application consists of 3 parts: -When your user signs in, call [signInWithOAuth()](/docs/reference/javascript/auth-signinwithoauth) with `bitbucket` as the `provider`: +When your user signs in, call [`signInWithOAuth()`](/docs/reference/javascript/auth-signinwithoauth) with `bitbucket` as the `provider`: ```js async function signInWithBitbucket() { @@ -70,7 +70,7 @@ async function signInWithBitbucket() { -When your user signs in, call [signInWithOAuth()](/docs/reference/dart/auth-signinwithoauth) with `bitbucket` as the `provider`: +When your user signs in, call [`signInWithOAuth()`](/docs/reference/dart/auth-signinwithoauth) with `bitbucket` as the `provider`: ```dart Future signInWithBitbucket() async { diff --git a/apps/docs/content/guides/auth/social-login/auth-discord.mdx b/apps/docs/content/guides/auth/social-login/auth-discord.mdx index 6dd8161b465..9712a6c76ae 100644 --- a/apps/docs/content/guides/auth/social-login/auth-discord.mdx +++ b/apps/docs/content/guides/auth/social-login/auth-discord.mdx @@ -56,7 +56,7 @@ Setting up Discord logins for your application consists of 3 parts: -When your user signs in, call [signInWithOAuth()](/docs/reference/javascript/auth-signinwithoauth) with `discord` as the `provider`: +When your user signs in, call [`signInWithOAuth()`](/docs/reference/javascript/auth-signinwithoauth) with `discord` as the `provider`: ```js async function signInWithDiscord() { @@ -69,7 +69,7 @@ async function signInWithDiscord() { -When your user signs in, call [signInWithOAuth()](/docs/reference/dart/auth-signinwithoauth) with `discord` as the `provider`: +When your user signs in, call [`signInWithOAuth()`](/docs/reference/dart/auth-signinwithoauth) with `discord` as the `provider`: ```dart Future signInWithDiscord() async { diff --git a/apps/docs/content/guides/auth/social-login/auth-facebook.mdx b/apps/docs/content/guides/auth/social-login/auth-facebook.mdx index 420bf7511d0..419533140e0 100644 --- a/apps/docs/content/guides/auth/social-login/auth-facebook.mdx +++ b/apps/docs/content/guides/auth/social-login/auth-facebook.mdx @@ -73,7 +73,7 @@ Under `Build Your App`, click on `Use Cases` screen. From there, do the followin -When your user signs in, call [signInWithOAuth()](/docs/reference/javascript/auth-signinwithoauth) with `facebook` as the `provider`: +When your user signs in, call [`signInWithOAuth()`](/docs/reference/javascript/auth-signinwithoauth) with `facebook` as the `provider`: ```js async function signInWithFacebook() { @@ -86,7 +86,7 @@ async function signInWithFacebook() { -When your user signs in, call [signInWithOAuth()](/docs/reference/dart/auth-signinwithoauth) with `facebook` as the `provider`: +When your user signs in, call [`signInWithOAuth()`](/docs/reference/dart/auth-signinwithoauth) with `facebook` as the `provider`: ```dart Future signInWithFacebook() async { @@ -102,7 +102,7 @@ Future signInWithFacebook() async { -When your user signs in, call [signInWithOAuth()](/docs/reference/swift/auth-signinwithoauth) with `facebook` as the `provider`: +When your user signs in, call [`signInWithOAuth()`](/docs/reference/swift/auth-signinwithoauth) with `facebook` as the `provider`: ```swift func signInWithFacebook() async throws { diff --git a/apps/docs/content/guides/auth/social-login/auth-figma.mdx b/apps/docs/content/guides/auth/social-login/auth-figma.mdx index ea3f979930a..fc5b9859acc 100644 --- a/apps/docs/content/guides/auth/social-login/auth-figma.mdx +++ b/apps/docs/content/guides/auth/social-login/auth-figma.mdx @@ -57,7 +57,7 @@ Setting up Figma logins for your application consists of 3 parts: -When your user signs in, call [signInWithOAuth()](/docs/reference/javascript/auth-signinwithoauth) with `figma` as the `provider`: +When your user signs in, call [`signInWithOAuth()`](/docs/reference/javascript/auth-signinwithoauth) with `figma` as the `provider`: ```js async function signInWithFigma() { @@ -70,7 +70,7 @@ async function signInWithFigma() { -When your user signs in, call [signInWithOAuth()](/docs/reference/flutter/auth-signinwithoauth) with `figma` as the `provider`: +When your user signs in, call [`signInWithOAuth()`](/docs/reference/flutter/auth-signinwithoauth) with `figma` as the `provider`: ```dart Future signInWithFigma() async { diff --git a/apps/docs/content/guides/auth/social-login/auth-github.mdx b/apps/docs/content/guides/auth/social-login/auth-github.mdx index 70b3e077f61..8b86db5c659 100644 --- a/apps/docs/content/guides/auth/social-login/auth-github.mdx +++ b/apps/docs/content/guides/auth/social-login/auth-github.mdx @@ -51,7 +51,7 @@ Copy your new OAuth credentials -When your user signs in, call [signInWithOAuth()](/docs/reference/javascript/auth-signinwithoauth) with `github` as the `provider`: +When your user signs in, call [`signInWithOAuth()`](/docs/reference/javascript/auth-signinwithoauth) with `github` as the `provider`: ```js async function signInWithGithub() { @@ -64,7 +64,7 @@ async function signInWithGithub() { -When your user signs in, call [signInWithOAuth()](/docs/reference/dart/auth-signinwithoauth) with `github` as the `provider`: +When your user signs in, call [`signInWithOAuth()`](/docs/reference/dart/auth-signinwithoauth) with `github` as the `provider`: ```dart Future signInWithGithub() async { @@ -77,6 +77,20 @@ Future signInWithGithub() async { } ``` + + + +When your user signs in, call [`signInWithOAuth`](/docs/reference/swift/auth-signinwithoauth) with `.github` as the `Provider`: + +```swift +func signInWithGithub() async throws { + try await supabase.auth.signInWithOAuth( + provider: .github, + redirectTo: URL(string: "my-custom-scheme://my-app-host") + ) +} +``` + diff --git a/apps/docs/content/guides/auth/social-login/auth-gitlab.mdx b/apps/docs/content/guides/auth/social-login/auth-gitlab.mdx index 067bbaeab92..7b68d6a8b84 100644 --- a/apps/docs/content/guides/auth/social-login/auth-gitlab.mdx +++ b/apps/docs/content/guides/auth/social-login/auth-gitlab.mdx @@ -54,7 +54,7 @@ Setting up GitLab logins for your application consists of 3 parts: -When your user signs in, call [signInWithOAuth()](/docs/reference/javascript/auth-signinwithoauth) with `gitlab` as the `provider`: +When your user signs in, call [`signInWithOAuth()`](/docs/reference/javascript/auth-signinwithoauth) with `gitlab` as the `provider`: ```js async function signInWithGitLab() { @@ -67,7 +67,7 @@ async function signInWithGitLab() { -When your user signs in, call [signInWithOAuth()](/docs/reference/dart/auth-signinwithoauth) with `gitlab` as the `provider`: +When your user signs in, call [`signInWithOAuth()`](/docs/reference/dart/auth-signinwithoauth) with `gitlab` as the `provider`: ```dart Future signInWithGitLab() async { diff --git a/apps/docs/content/guides/auth/social-login/auth-google.mdx b/apps/docs/content/guides/auth/social-login/auth-google.mdx index e747e597111..ca2f562edef 100644 --- a/apps/docs/content/guides/auth/social-login/auth-google.mdx +++ b/apps/docs/content/guides/auth/social-login/auth-google.mdx @@ -27,7 +27,7 @@ To support Sign In with Google, you need to configure the Google provider for yo For web applications, you can set up your signin button two different ways: - Use your own [application code](#application-code) for the button. -- Use [Google's pre-built sign-in or OneTap flows](#google-pre-built). +- Use [Google's pre-built sign-in or One Tap flows](#google-pre-built). ### Application code configuration @@ -69,7 +69,7 @@ To use Google's pre-built signin buttons: 1. Click `Create credentials` and choose `OAuth Client ID`. 1. For application type, choose `Web application`. 1. Under **Authorized JavaScript origins** and **Authorized redirect URLs**, add your site URL. This is the URL of the website where the signin button will appear, _not_ your Supabase project domain. If you're testing in localhost, ensure that you have `http://localhost` set in the **Authorized JavaScript origins** section as well. This is important when integrating with Google One-Tap to ensure you can use it locally. -1. When you finish configuring your credentials, you will be shown your client ID. Add this to the **Authorized Client IDs** field in the [Google Auth Provider section of the Supabase Dashboard](/dashboard/project/_/auth/providers). Leave the OAuth client ID and secret blank. You don't need them when using Google's pre-built approach. +1. When you finish configuring your credentials, you will be shown your client ID. Add this to the **Client IDs** field in the [Google Auth Provider section of the Supabase Dashboard](/dashboard/project/_/auth/providers). Leave the OAuth client ID and secret blank. You don't need them when using Google's pre-built approach. @@ -78,9 +78,9 @@ To use Google's pre-built signin buttons: 1. Configure OAuth credentials for your Google Cloud project in the [Credentials](https://console.cloud.google.com/apis/credentials) page of the console. When creating a new OAuth client ID, choose _Android_ or _iOS_ depending on the mobile operating system your app is built for. - For Android, use the instructions on screen to provide the SHA-1 certificate fingerprint used to sign your Android app. - You will have a different set of SHA-1 certificate fingerprint for testing locally and going to production. Make sure to add both to the Google Cloud Console. and add all of the Client IDs to Supabase dashboard. - - For iOS, use the instructions on screen to provide the app Bundle ID, and App Store ID and Team ID if the app is already published on the Apple AppStore. + - For iOS, use the instructions on screen to provide the app Bundle ID, and App Store ID and Team ID if the app is already published on the Apple App Store. 2. Configure the [OAuth Consent Screen](https://console.cloud.google.com/apis/credentials/consent). This information is shown to the user when giving consent to your app. In particular, make sure you have set up links to your app's privacy policy and terms of service. - 3. Finally, add the client ID from step 1 in the [Google provider on the Supabase Dashboard](https://supabase.com/dashboard/project/_/auth/providers), under _Authorized Client IDs_. + 3. Finally, add the client ID from step 1 in the [Google provider on the Supabase Dashboard](https://supabase.com/dashboard/project/_/auth/providers), under _Client IDs_. Note that you do not have to configure the OAuth flow in the Supabase Dashboard in order to use native sign in. @@ -94,9 +94,9 @@ To use Google's pre-built signin buttons: - For both Android and iOS, you will need to create a Web client ID in the [Google Cloud Console](https://console.cloud.google.com/apis/credentials). - For Android, use the instructions on screen to provide the SHA-1 certificate fingerprint used to sign your Android app. - You will have a different set of SHA-1 certificate fingerprint for testing locally and going to production. Make sure to add both to the Google Cloud Console. and add all of the Client IDs to Supabase dashboard. - - For iOS, use the instructions on screen to provide the app Bundle ID, and App Store ID and Team ID if the app is already published on the Apple AppStore. + - For iOS, use the instructions on screen to provide the app Bundle ID, and App Store ID and Team ID if the app is already published on the Apple App Store. 2. Configure the [OAuth Consent Screen](https://console.cloud.google.com/apis/credentials/consent). This information is shown to the user when giving consent to your app. In particular, make sure you have set up links to your app's privacy policy and terms of service. - 3. Add only the web client ID from step 1 in the [Google provider on the Supabase Dashboard](https://supabase.com/dashboard/project/_/auth/providers), under _Authorized Client IDs_. If you need iOS support, enable the `Skip nonce check` option. + 3. Add only the web client ID from step 1 in the [Google provider on the Supabase Dashboard](https://supabase.com/dashboard/project/_/auth/providers), under _Client IDs_. If you need iOS support, enable the `Skip nonce check` option. 4. For iOS, add the `CFBundleURLTypes` attributes below into the `/ios/Runner/Info.plist` file. ```xml @@ -129,7 +129,7 @@ To use Google's pre-built signin buttons: - Google sign-in with Supabase is done through the [GoogleSignIn-iOS](https://github.com/google/GoogleSignIn-iOS) package. + Google sign-in with Supabase is done through the [`GoogleSignIn-iOS`](https://github.com/google/GoogleSignIn-iOS) package. When the user provides consent, Google issues an identity token (commonly abbreviated as ID token) that is then sent to your project's Supabase Auth server. When valid, a new user session is started by issuing an access and refresh token from Supabase Auth. @@ -166,9 +166,9 @@ To use Google's pre-built signin buttons: ### Configuration [#ios-configuration] - 1. Follow the integration instructions on the [get started with Google Sign-In](https://developers.google.com/identity/sign-in/ios/start-integrating) for iOS guide. + 1. Follow the integration instructions on the [get started with Google Sign-In](https://developers.google.com/identity/sign-in/ios/start-integrating) for the iOS guide. 2. Configure the [OAuth Consent Screen](https://console.cloud.google.com/apis/credentials/consent). This information is shown to the user when giving consent to your app. In particular, make sure you have set up links to your app's privacy policy and terms of service. - 3. Add only the web client ID from step 1 in the [Google provider on the Supabase Dashboard](https://supabase.com/dashboard/project/_/auth/providers), under _Authorized Client IDs_. Enable the `Skip nonce check` option. + 3. Add web client ID and iOS client ID from step 1 in the [Google provider on the Supabase Dashboard](https://supabase.com/dashboard/project/_/auth/providers), under _Client IDs_, separated by a comma. Enable the `Skip nonce check` option. @@ -178,7 +178,7 @@ To use Google's pre-built signin buttons: 1. Configure OAuth credentials for your Google Cloud project in the [Credentials](https://console.cloud.google.com/apis/credentials) page of the console. You need two client IDs, a web client ID and Android client ID. When creating a new Android OAuth client ID, use the instructions on screen to provide the SHA-1 certificate fingerprint used to sign your Android app. 2. Configure the [OAuth Consent Screen](https://console.cloud.google.com/apis/credentials/consent). This information is shown to the user when giving consent to your app. In particular, make sure you have set up links to your app's privacy policy and terms of service. - 3. Finally, add the web client ID from step 1 in the [Google provider on the Supabase Dashboard](https://supabase.com/dashboard/project/_/auth/providers), under _Authorized Client IDs_. + 3. Finally, add the web client ID from step 1 in the [Google provider on the Supabase Dashboard](https://supabase.com/dashboard/project/_/auth/providers), under _Client IDs_. Note that you do not have to configure the OAuth flow in the Supabase Dashboard in order to use native sign in. @@ -187,10 +187,10 @@ To use Google's pre-built signin buttons: 1. Configure OAuth credentials for your Google Cloud project in the [Credentials](https://console.cloud.google.com/apis/credentials) page of the console. When creating a new OAuth client ID, choose _Android_ or _iOS_ depending on the mobile operating system your app is built for. - For Android, use the instructions on screen to provide the SHA-1 certificate fingerprint used to sign your Android app. - - For iOS, use the instructions on screen to provide the app Bundle ID, and App Store ID and Team ID if the app is already published on the Apple AppStore. + - For iOS, use the instructions on screen to provide the app Bundle ID, and App Store ID and Team ID if the app is already published on the Apple App Store. 2. Configure the [OAuth Consent Screen](https://console.cloud.google.com/apis/credentials/consent). This information is shown to the user when giving consent to your app. In particular, make sure you have set up links to your app's privacy policy and terms of service. - 3. Finally, add the client ID from step 1 in the [Google provider on the Supabase Dashboard](https://supabase.com/dashboard/project/_/auth/providers), under _Authorized Client IDs_. + 3. Finally, add the client ID from step 1 in the [Google provider on the Supabase Dashboard](https://supabase.com/dashboard/project/_/auth/providers), under _Client IDs_. Note that you do not have to configure the OAuth flow in the Supabase Dashboard in order to use native sign in. @@ -202,7 +202,7 @@ To use Google's pre-built signin buttons: 1. Configure OAuth credentials for your Google Cloud project in the [Credentials](https://console.cloud.google.com/apis/credentials) page of the console. When creating a new OAuth client ID, choose _Chrome extension_ for the application type. For _Item ID_ provide the unique ID of your Chrome extension. You can get this by calling `chrome.runtime.id` within the extension, or from the Web Store URL of the extension. For example, the [Google Translate extension](https://chrome.google.com/webstore/detail/google-translate/aapbdbdomjkkjkaonfhkkikfgjllcleb) has the Web Store URL `https://chrome.google.com/webstore/detail/google-translate/aapbdbdomjkkjkaonfhkkikfgjllcleb` and the last part `aapbdbdomjkkjkaonfhkkikfgjllcleb` is its unique ID. 2. Configure the [OAuth Consent Screen](https://console.cloud.google.com/apis/credentials/consent). This information is shown to the user when giving consent to your app. - 3. Finally, add the client ID from step 1 in the [Google provider on the Supabase Dashboard](https://supabase.com/dashboard/project/_/auth/providers), under _Authorized Client IDs_. + 3. Finally, add the client ID from step 1 in the [Google provider on the Supabase Dashboard](https://supabase.com/dashboard/project/_/auth/providers), under _Client IDs_. Note that you do not have to configure the OAuth flow in the Supabase Dashboard to sign in with Google inside Chrome extensions. @@ -271,7 +271,7 @@ Most web apps and websites can utilize Google's [personalized sign-in buttons](h 1. Use the [HTML Code Generator](https://developers.google.com/identity/gsi/web/tools/configurator) to customize the look, feel, features and behavior of the Sign in with Google button. 1. Pick the _Swap to JavaScript callback_ option, and input the name of your callback function. This function will receive a [`CredentialResponse`](https://developers.google.com/identity/gsi/web/reference/js-reference#CredentialResponse) when sign in completes. - To make your app compatible with Chome's third-party-cookie phase-out, make sure to set `data-use_fedcm_for_prompt` to `true`. + To make your app compatible with Chrome's third-party-cookie phase-out, make sure to set `data-use_fedcm_for_prompt` to `true`. Your final HTML code might look something like this: @@ -341,9 +341,9 @@ Most web apps and websites can utilize Google's [personalized sign-in buttons](h // Use 'nonce' when invoking the supabase.auth.signInWithIdToken() method ``` -### One-tap with NextJS +### One-tap with Next.js -If you're integrating Google One-Tap with your NextJS application, you can refer to the example below to get started: +If you're integrating Google One-Tap with your Next.js application, you can refer to the example below to get started: ```tsx 'use client' @@ -439,6 +439,8 @@ Unlike the OAuth flow which requires the use of a web browser, the native Sign i When the user provides consent, Google issues an identity token (commonly abbreviated as ID token) that is then sent to your project's Supabase Auth server. When valid, a new user session is started by issuing an access and refresh token from Supabase Auth. +By default, Supabase Auth implements nonce validation during the authentication flow. This can be disabled in production under `Authentication > Providers > Google > Skip Nonce Check` in the Dashboard, or when developing locally by setting `auth.external..skip_nonce_check`. Only disable this if your client libraries cannot properly handle nonce verification. + When working with Expo, you can use the [`react-native-google-signin/google-signin` library](https://github.com/react-native-google-signin/google-signin#expo-installation) library to obtain an ID token that you can pass to supabase-js [`signInWithIdToken` method](/docs/reference/javascript/auth-signinwithidtoken). Follow the [Expo installation docs](https://react-native-google-signin.github.io/docs/setting-up/expo) for installation and configuration instructions. See the [supabase-js reference](/docs/reference/javascript/initializing?example=react-native-options-async-storage) for instructions on initializing the supabase-js client in React Native. @@ -465,10 +467,10 @@ export default function () { try { await GoogleSignin.hasPlayServices() const userInfo = await GoogleSignin.signIn() - if (userInfo.idToken) { + if (userInfo.data.idToken) { const { data, error } = await supabase.auth.signInWithIdToken({ provider: 'google', - token: userInfo.idToken, + token: userInfo.data.idToken, }) console.log(error, data) } else { @@ -546,7 +548,7 @@ Future _nativeGoogleSignIn() async { ### Web, macOS, Windows, and Linux -Google sign-in with Supabase on Web, macOS, Windows, and Linux is done through the [signInWithOAuth](docs/reference/dart/auth-signinwithoauth) method. +Google sign-in with Supabase on Web, macOS, Windows, and Linux is done through the [`signInWithOAuth`](docs/reference/dart/auth-signinwithoauth) method. This method of signing in is web based, and will open a browser window to perform the sign in. For non-web platforms, the user is brought back to the app via [deep linking](/docs/guides/auth/native-mobile-deep-linking?platform=flutter). @@ -593,7 +595,7 @@ implementation ("com.google.android.libraries.identity.googleid:googleid:") ``` -Add the following proguard rules to your `proguard-rules.pro` file: +Add the following ProGuard rules to your `proguard-rules.pro` file: ```proguard -if class androidx.credentials.CredentialManager @@ -776,3 +778,11 @@ chrome.identity.launchWebAuthFlow( + +## Google consent screen + +![Google Consent Screen](/docs/img/guides/auth-google/auth-google-consent-screen.png) + +By default, the Google consent screen shows the root domain of the callback URL, where Google will send the authentication response. With Supabase Auth, it is your Supabase project's domain `(https://.supabase.co)`. + +If that is not preferable, you can use a [Custom Domain](https://supabase.com/docs/guides/platform/custom-domains) with your Supabase project. You can use it as your project's domain when creating the Supabase client in your application and initiating the authentication flow. It will then show up in the Google consent screen. If you want your app name and the logo on the consent screen, [you must submit your app to Google for verification](https://support.google.com/cloud/answer/10311615). diff --git a/apps/docs/content/guides/auth/social-login/auth-kakao.mdx b/apps/docs/content/guides/auth/social-login/auth-kakao.mdx index ca54ed737dc..9b678996d95 100644 --- a/apps/docs/content/guides/auth/social-login/auth-kakao.mdx +++ b/apps/docs/content/guides/auth/social-login/auth-kakao.mdx @@ -52,7 +52,7 @@ This will serve as the `client_id` when you make API calls to authenticate the u - Go to `Product settings` > `Kakao Login` > `Security`. - Click on the `Kakao Login` switch to enable Kakao Login. -- Click on `generate code` at the bottom to generate the `Client secret code` -- this will serve as a `client_secret` for your supabase project. +- Click on `generate code` at the bottom to generate the `Client secret code` -- this will serve as a `client_secret` for your Supabase project. - Make sure you enabled `Client secret code` by selecting `enable` from the `Activation state` section. ## Additional configurations on Kakao Developers portal @@ -79,7 +79,7 @@ This will serve as the `client_id` when you make API calls to authenticate the u -When your user signs in, call [signInWithOAuth()](/docs/reference/javascript/auth-signinwithoauth) with `kakao` as the `provider`: +When your user signs in, call [`signInWithOAuth()`](/docs/reference/javascript/auth-signinwithoauth) with `kakao` as the `provider`: ```js async function signInWithKakao() { @@ -92,7 +92,7 @@ async function signInWithKakao() { -When your user signs in, call [signInWithOAuth()](/docs/reference/dart/auth-signinwithoauth) with `kakao` as the `provider`: +When your user signs in, call [`signInWithOAuth()`](/docs/reference/dart/auth-signinwithoauth) with `kakao` as the `provider`: ```dart Future signInWithKakao() async { diff --git a/apps/docs/content/guides/auth/social-login/auth-keycloak.mdx b/apps/docs/content/guides/auth/social-login/auth-keycloak.mdx index 06750243cb2..43b21bac65e 100644 --- a/apps/docs/content/guides/auth/social-login/auth-keycloak.mdx +++ b/apps/docs/content/guides/auth/social-login/auth-keycloak.mdx @@ -10,16 +10,16 @@ To enable Keycloak Auth for your project, you need to set up an Keycloak OAuth a To get started with Keycloak, you can run it in a docker container with: `docker run -p 8080:8080 -e KEYCLOAK_ADMIN=admin -e KEYCLOAK_ADMIN_PASSWORD=admin quay.io/keycloak/keycloak:latest start-dev` -This guide will be assuming that you are running keycloak in a docker container as described in the command above. +This guide will be assuming that you are running Keycloak in a docker container as described in the command above. Keycloak OAuth consists of five broad steps: -- Create a new client in your specified keycloak realm. +- Create a new client in your specified Keycloak realm. - Obtain the `issuer` from the "OpenID Endpoint Configuration". This will be used as the `Keycloak URL`. -- Ensure that the new client has the "Client Protocol" set to "openid-connect" and the "Access Type" is set to "confidential". +- Ensure that the new client has the "Client Protocol" set to `openid-connect` and the "Access Type" is set to "confidential". - The `Client ID` of the client created will be used as the `client id`. - Obtain the `Secret` from the credentials tab which will be used as the `client secret`. -- Add the callback url of your application to your allowlist. +- Add the callback URL of your application to your allowlist. ## Access your Keycloak admin console @@ -43,7 +43,7 @@ The "Client ID" of the created client will serve as the `client_id` when you mak After you've created the client successfully, ensure that you set the following settings: -1. The "Client Protocol" should be set to "openid-connect". +1. The "Client Protocol" should be set to `openid-connect`. 2. The "Access Type" should be set to "confidential". 3. The "Valid Redirect URIs" should be set to: `https://.supabase.co/auth/v1/callback`. @@ -72,7 +72,7 @@ Since Keycloak version 22, the `openid` scope must be passed. Add this to the [` -When your user signs in, call [signInWithOAuth()](/docs/reference/javascript/auth-signinwithoauth) with `keycloak` as the `provider`: +When your user signs in, call [`signInWithOAuth()`](/docs/reference/javascript/auth-signinwithoauth) with `keycloak` as the `provider`: ```js async function signInWithKeycloak() { @@ -88,7 +88,7 @@ async function signInWithKeycloak() { -When your user signs in, call [signInWithOAuth()](/docs/reference/dart/auth-signinwithoauth) with `keycloak` as the `provider`: +When your user signs in, call [`signInWithOAuth()`](/docs/reference/dart/auth-signinwithoauth) with `keycloak` as the `provider`: ```dart Future signInWithKeycloak() async { @@ -163,5 +163,5 @@ suspend fun signOut() { ## Resources -- You can find the keycloak openid endpoint configuration under the realm settings. +- You can find the Keycloak OpenID endpoint configuration under the realm settings. ![Keycloak OpenID Endpoint Configuration](/docs/img/guides/auth-keycloak/keycloak-openid-endpoint-config.png) diff --git a/apps/docs/content/guides/auth/social-login/auth-linkedin.mdx b/apps/docs/content/guides/auth/social-login/auth-linkedin.mdx index 3e80f7dd1ce..0aa636294e1 100644 --- a/apps/docs/content/guides/auth/social-login/auth-linkedin.mdx +++ b/apps/docs/content/guides/auth/social-login/auth-linkedin.mdx @@ -8,12 +8,6 @@ To enable LinkedIn Auth for your project, you need to set up a LinkedIn OAuth ap ## Overview - - -We will be replacing the existing _LinkedIn_ provider with a new _LinkedIn (OIDC)_ provider. Developers with LinkedIn OAuth Applications created prior to 1st August 2023 should create a new OAuth application and migrate their credentials from the _LinkedIn_ provider to the _LinkedIn (OIDC)_ provider. Alternatively, you can also add the newly released `Sign In with LinkedIn using OpenID Connect` to your existing OAuth application. [Read this section](/docs/guides/auth/social-login/auth-linkedin#linkedin-open-id-connect-oidc) to find out more. - - - Setting up LinkedIn logins for your application consists of 3 parts: - Create and configure a LinkedIn Project and App on the [LinkedIn Developer Dashboard](https://www.linkedin.com/developers/apps). @@ -65,7 +59,7 @@ Ensure that the appropriate scopes have been added under OAuth 2.0 Scopes at the -When your user signs in, call [signInWithOAuth()](/docs/reference/javascript/auth-signinwithoauth) with `linkedin_oidc` as the `provider`: +When your user signs in, call [`signInWithOAuth()`](/docs/reference/javascript/auth-signinwithoauth) with `linkedin_oidc` as the `provider`: ```js async function signInWithLinkedIn() { @@ -78,7 +72,7 @@ async function signInWithLinkedIn() { -When your user signs in, call [signInWithOAuth()](/docs/reference/dart/auth-signinwithoauth) with `linkedin_oidc` as the `provider`: +When your user signs in, call [`signInWithOAuth()`](/docs/reference/dart/auth-signinwithoauth) with `linkedin_oidc` as the `provider`: ```dart Future signInWithLinkedIn() async { diff --git a/apps/docs/content/guides/auth/social-login/auth-notion.mdx b/apps/docs/content/guides/auth/social-login/auth-notion.mdx index 91674a09e1a..c542db2e633 100644 --- a/apps/docs/content/guides/auth/social-login/auth-notion.mdx +++ b/apps/docs/content/guides/auth/social-login/auth-notion.mdx @@ -23,7 +23,7 @@ Setting up Notion logins for your application consists of 3 parts: - Once logged in, go to [notion.so/my-integrations](https://notion.so/my-integrations) and create a new integration. - When creating your integration, ensure that you select "Public integration" under "Integration type" and "Read user information including email addresses" under "Capabilities". -- You will need to add a redirect uri, see [Add the redirect uri](#add-the-redirect-uri) +- You will need to add a redirect URI, see [Add the redirect URI](#add-the-redirect-uri) - Once you've filled in the necessary fields, click "Submit" to finish creating the integration. ![notion.so](/docs/img/guides/auth-notion/notion-developer.png) @@ -57,7 +57,7 @@ Setting up Notion logins for your application consists of 3 parts: -When your user signs in, call [signInWithOAuth()](/docs/reference/javascript/auth-signinwithoauth) with `notion` as the `provider`: +When your user signs in, call [`signInWithOAuth()`](/docs/reference/javascript/auth-signinwithoauth) with `notion` as the `provider`: ```js async function signInWithNotion() { @@ -70,7 +70,7 @@ async function signInWithNotion() { -When your user signs in, call [signInWithOAuth()](/docs/reference/dart/auth-signinwithoauth) with `notion` as the `provider`: +When your user signs in, call [`signInWithOAuth()`](/docs/reference/dart/auth-signinwithoauth) with `notion` as the `provider`: ```dart Future signInWithNotion() async { diff --git a/apps/docs/content/guides/auth/social-login/auth-slack.mdx b/apps/docs/content/guides/auth/social-login/auth-slack.mdx index ea858493257..40d4bdce6ed 100644 --- a/apps/docs/content/guides/auth/social-login/auth-slack.mdx +++ b/apps/docs/content/guides/auth/social-login/auth-slack.mdx @@ -75,7 +75,7 @@ Under `Scopes`: -When your user signs in, call [signInWithOAuth()](/docs/reference/javascript/auth-signinwithoauth) with `slack_oidc` as the `provider`: +When your user signs in, call [`signInWithOAuth()`](/docs/reference/javascript/auth-signinwithoauth) with `slack_oidc` as the `provider`: ```js async function signInWithSlack() { @@ -88,7 +88,7 @@ async function signInWithSlack() { -When your user signs in, call [signInWithOAuth()](/docs/reference/dart/auth-signinwithoauth) with `slack` as the `provider`: +When your user signs in, call [`signInWithOAuth()`](/docs/reference/dart/auth-signinwithoauth) with `slack` as the `provider`: ```dart Future signInWithSlack() async { diff --git a/apps/docs/content/guides/auth/social-login/auth-spotify.mdx b/apps/docs/content/guides/auth/social-login/auth-spotify.mdx index 987492dde18..fe0df251278 100644 --- a/apps/docs/content/guides/auth/social-login/auth-spotify.mdx +++ b/apps/docs/content/guides/auth/social-login/auth-spotify.mdx @@ -69,7 +69,7 @@ The following outlines the steps to sign in using Spotify with Supabase Auth. -When your user signs in, call [signInWithOAuth()](/docs/reference/javascript/auth-signinwithoauth) with `spotify` as the `provider`: +When your user signs in, call [`signInWithOAuth()`](/docs/reference/javascript/auth-signinwithoauth) with `spotify` as the `provider`: ```js async function signInWithSpotify() { @@ -82,7 +82,7 @@ async function signInWithSpotify() { -When your user signs in, call [signInWithOAuth()](/docs/reference/dart/auth-signinwithoauth) with `spotify` as the `provider`: +When your user signs in, call [`signInWithOAuth()`](/docs/reference/dart/auth-signinwithoauth) with `spotify` as the `provider`: ```dart Future signInWithSpotify() async { diff --git a/apps/docs/content/guides/auth/social-login/auth-twitch.mdx b/apps/docs/content/guides/auth/social-login/auth-twitch.mdx index 97f17026a45..a1c2590143b 100644 --- a/apps/docs/content/guides/auth/social-login/auth-twitch.mdx +++ b/apps/docs/content/guides/auth/social-login/auth-twitch.mdx @@ -41,7 +41,7 @@ Setting up Twitch logins for your application consists of 3 parts: - Enter the name of your application. - Type or paste your `OAuth Redirect URL` (the callback URL from the previous step.) - Select a category for your app. -- Check the Captcha box and click `Create`. +- Check the CAPTCHA box and click `Create`. ## Retrieve your Twitch OAuth client ID and client secret @@ -72,7 +72,7 @@ Setting up Twitch logins for your application consists of 3 parts: -When your user signs in, call [signInWithOAuth()](/docs/reference/javascript/auth-signinwithoauth) with `twitch` as the `provider`: +When your user signs in, call [`signInWithOAuth()`](/docs/reference/javascript/auth-signinwithoauth) with `twitch` as the `provider`: ```js async function signInWithTwitch() { @@ -85,7 +85,7 @@ async function signInWithTwitch() { -When your user signs in, call [signInWithOAuth()](/docs/reference/dart/auth-signinwithoauth) with `twitch` as the `provider`: +When your user signs in, call [`signInWithOAuth()`](/docs/reference/dart/auth-signinwithoauth) with `twitch` as the `provider`: ```dart Future signInWithTwitch() async { diff --git a/apps/docs/content/guides/auth/social-login/auth-twitter.mdx b/apps/docs/content/guides/auth/social-login/auth-twitter.mdx index 0cd3fe30edb..cf8e34c7531 100644 --- a/apps/docs/content/guides/auth/social-login/auth-twitter.mdx +++ b/apps/docs/content/guides/auth/social-login/auth-twitter.mdx @@ -63,7 +63,7 @@ Setting up Twitter logins for your application consists of 3 parts: -When your user signs in, call [signInWithOAuth()](/docs/reference/javascript/auth-signinwithoauth) with `twitter` as the `provider`: +When your user signs in, call [`signInWithOAuth()`](/docs/reference/javascript/auth-signinwithoauth) with `twitter` as the `provider`: ```js async function signInWithTwitter() { @@ -76,7 +76,7 @@ async function signInWithTwitter() { -When your user signs in, call [signInWithOAuth()](/docs/reference/dart/auth-signinwithoauth) with `twitter` as the `provider`: +When your user signs in, call [`signInWithOAuth()`](/docs/reference/dart/auth-signinwithoauth) with `twitter` as the `provider`: ```dart Future signInWithTwitter() async { diff --git a/apps/docs/content/guides/auth/social-login/auth-workos.mdx b/apps/docs/content/guides/auth/social-login/auth-workos.mdx index f09fd573980..1abd6df0c0b 100644 --- a/apps/docs/content/guides/auth/social-login/auth-workos.mdx +++ b/apps/docs/content/guides/auth/social-login/auth-workos.mdx @@ -1,172 +1,116 @@ --- -id: 'auth-workos' -title: 'Login with WorkOS' -description: 'Add WorkOS OAuth to your Supabase project' +title: 'SSO and Social Login with WorkOS' --- -To enable WorkOS Auth for your project, you need to set up WorkOS OAuth application and add the application credentials to your Supabase Dashboard. +## Use Social Login with WorkOS -## Overview +### Step 1. Create a WorkOS organization -In this guide, we will cover how to use Supabase OAuth with WorkOS to implement Single-Sign-On(SSO). - -The procedure consists of five broad steps: - -- Create a new organization from your WorkOS Dashboard. -- Obtain the `Client ID` from the Configuration tab and configure redirect URI. -- Obtain the `WorkOS Secret` from the credentials tab. -- Connect a WorkOS Supported Identity Provider -- Add your WorkOS credentials into your Supabase project - -## Create a WorkOS organization - -Log in to the dashboard and hop over to the Organizations tab to create and organization +Log in to the WorkOS dashboard and visit the Organizations tab to create an organization. ![Create an Organization](/docs/img/guides/auth-workos/workos-create-organization.png) -## Obtain the client ID and configure redirect URI +Alternatively, you can [create an organization via the WorkOS API](https://workos.com/docs/reference/organization/create). -Head over to the Configuration tab and configure the redirect URI.The redirect URI should look like `https://.supabase.co/auth/v1/callback` -Note that this is distinct from the redirect URI referred to in the Supabase dashboard +## Step 2. Obtain your `Client ID` and `WORKOS_API_KEY` values -![Fetch Client ID and configure Redirect URI](/docs/img/guides/auth-workos/workos-clientid-redirect-uri.png) +![Get your Environment's Client ID and Secret](/docs/img/guides/auth-workos/workos-dashboard-get-client-id-and-key.png) -## Obtain the WorkOS secret +Visit the getting started page of the [WorkOS Dashboard](https://dashboard.workos.com/get-started). Copy the following values from the Quickstart panel: -Head over to the API Keys page and obtain the secret key. +- `WORKOS_CLIENT_ID` +- `WORKOS_API_KEY` -![WorkOS Secret Key](/docs/img/guides/auth-workos/workos-secret-key.png) + -## Connect a WorkOS supported identity provider +You must be signed in to see these values. -Set up the identity provider by visiting the setup link. + -![Visiting the setup link](/docs/img/guides/auth-workos/workos-setup-identity-provider.png) +## Step 3. Add your WorkOS credentials to your Supabase project -You can pick between any one of the many identity providers that WorkOS supports. +![Enter your WorkOS application details in your Supabase app's auth provider settings panel](/docs/img/guides/auth-workos/supabase-workos-configuration.png) -## Add your WorkOS credentials into your Supabase project +1. Go to your Supabase Project Dashboard. +1. In the left sidebar, click the Authentication icon (near the top). +1. Click on Providers under the Configuration section. +1. Click on WorkOS from the accordion list to expand. +1. Toggle the `WorkOS Enabled` switch to ON. +1. Enter `https://api.workos.com` in the WorkOS URL field. +1. Enter your WorkOS Client ID and WorkOS Client Secret saved in the previous step. +1. Copy the `Callback URL (for OAuth)` value from the form and save it somewhere handy. +1. Click Save. - +## Step 4. Set your Supabase redirect URI in the WorkOS Dashboard -## Add login code to your client app +Visit the WorkOS dashboard and click the redirects button in the left navigation panel. - - +On the redirects page, enter your Supabase project's `Callback URL (for OAuth)` which you saved in the previous step, as shown below: - +![Set your Supbase project redirect URL in the WorkOS dashboard](/docs/img/guides/auth-workos/workos-set-supabase-redirect.png) -When your user signs in, call [signInWithOAuth()](/docs/reference/javascript/auth-signinwithoauth) with `workos` as the `provider`: +## Step 5. Add login code to your client app -```js +When a user signs in, call `signInWithOAuth` with `workos` as the provider. + +```javascript async function signInWithWorkOS() { const { data, error } = await supabase.auth.signInWithOAuth({ provider: 'workos', options: { + redirectTo: 'http://example.com/auth/v1/callback', // Make sure your redirect URL is configured in the Supabase Dashboard Auth settings queryParams: { - connection: '', - organization: '', + connection: '', }, }, }) + + if (data.url) { + redirect(data.url) // use the redirect API for your server or framework + } } ``` -Refer to the [WorkOS Documentation](https://workos.com/docs/reference/sso/authorize/) to learn more about the different methods. + - - +You can find your `connection_id` in the WorkOS dashboard under the Organizations tab. Select your organization and then click View connection. -When your user signs in, call [signInWithOAuth()](/docs/reference/dart/auth-signinwithoauth) with `workos` as the `provider`: + -```dart -Future signInWithWorkOS() async { - await supabase.auth.signInWithOAuth( - OAuthProvider.workos, - redirectTo: kIsWeb ? null : 'my.scheme://my-host', // Optionally set the redirect link to bring back the user via deeplink. - authScreenLaunchMode: - kIsWeb ? LaunchMode.platformDefault : LaunchMode.externalApplication, // Launch the auth screen in a new webview on mobile. - queryParams: { - 'connection': '', - 'organization': '', - }, - ); +Within your specified callback URL, you'll exchange the code for a logged-in user profile: + +```javascript auth/v1/callback/route.ts +import { NextResponse } from 'next/server' +import { createClient } from '@/utils/supabase/server' + +export async function GET(request: Request) { + const { searchParams, origin } = new URL(request.url) + const code = searchParams.get('code') + // if "next" is in param, use it as the redirect URL + const next = searchParams.get('next') ?? '/' + + if (code) { + const supabase = await createClient() + const { error } = await supabase.auth.exchangeCodeForSession(code) + if (!error) { + const forwardedHost = request.headers.get('x-forwarded-host') // original origin before load balancer + const isLocalEnv = process.env.NODE_ENV === 'development' + if (isLocalEnv) { + // we can be sure that there is no load balancer in between, so no need to watch for X-Forwarded-Host + return NextResponse.redirect(`${origin}${next}`) + } else if (forwardedHost) { + return NextResponse.redirect(`https://${forwardedHost}${next}`) + } else { + return NextResponse.redirect(`${origin}${next}`) + } + } + } + + // return the user to an error page with instructions + return NextResponse.redirect(`${origin}/auth/auth-code-error`) } + ``` -Refer to the [WorkOS Documentation](https://workos.com/docs/reference/sso/authorize/) to learn more about the different methods. - - - - -When your user signs in, call [signInWith(Provider)](/docs/reference/kotlin/auth-signinwithoauth) with `WorkOS` as the `Provider`: - -```kotlin -suspend fun signInWithWorkOS() { - supabase.auth.signInWith(WorkOS) { - queryParams["connection"] = "" - queryParams["organization"] = "" - queryParams["workos_provider"] = "" - } -} -``` - -Refer to the [WorkOS Documentation](https://workos.com/docs/reference/sso/authorize/) to learn more about the different methods. - - - - - - - - - -When your user signs out, call [signOut()](/docs/reference/javascript/auth-signout) to remove them from the browser session and any objects from localStorage: - -```js -async function signOut() { - const { error } = await supabase.auth.signOut() -} -``` - - - - -When your user signs out, call [signOut()](/docs/reference/dart/auth-signout) to remove them from the browser session and any objects from localStorage: - -```dart -Future signOut() async { - await supabase.auth.signOut(); -} -``` - - - - -When your user signs out, call [signOut()](/docs/reference/kotlin/auth-signout) to remove them from the browser session and any objects from localStorage: - -```kotlin -suspend fun signOut() { - supabase.auth.signOut() -} -``` - - - - ## Resources - [WorkOS Documentation](https://workos.com/docs/sso/guide) diff --git a/apps/docs/content/guides/auth/social-login/auth-zoom.mdx b/apps/docs/content/guides/auth/social-login/auth-zoom.mdx index e10102550cc..6c3b9f33531 100644 --- a/apps/docs/content/guides/auth/social-login/auth-zoom.mdx +++ b/apps/docs/content/guides/auth/social-login/auth-zoom.mdx @@ -70,7 +70,7 @@ Under `Scopes` -When your user signs in, call [signInWithOAuth()](/docs/reference/javascript/auth-signinwithoauth) with `zoom` as the `provider`: +When your user signs in, call [`signInWithOAuth()`](/docs/reference/javascript/auth-signinwithoauth) with `zoom` as the `provider`: ```js async function signInWithZoom() { @@ -83,7 +83,7 @@ async function signInWithZoom() { -When your user signs in, call [signInWithOAuth()](/docs/reference/dart/auth-signinwithoauth) with `zoom` as the `provider`: +When your user signs in, call [`signInWithOAuth()`](/docs/reference/dart/auth-signinwithoauth) with `zoom` as the `provider`: ```dart Future signInWithZoom() async { diff --git a/apps/docs/content/guides/auth/third-party/aws-cognito.mdx b/apps/docs/content/guides/auth/third-party/aws-cognito.mdx index 1fe4f487771..183d5f8f6e6 100644 --- a/apps/docs/content/guides/auth/third-party/aws-cognito.mdx +++ b/apps/docs/content/guides/auth/third-party/aws-cognito.mdx @@ -140,7 +140,7 @@ user_pool_id = "" user_pool_region = "" ``` -## Use a Pre-Token Generation Trigger to assign the authenticated role +## Use a pre-token generation trigger to assign the authenticated role Your Supabase project inspects the `role` claim present in all JWTs sent to it, to assign the correct Postgres role when using the Data API, Storage or Realtime authorization. diff --git a/apps/docs/content/guides/auth/third-party/firebase-auth.mdx b/apps/docs/content/guides/auth/third-party/firebase-auth.mdx index f3762f9476e..92d89657837 100644 --- a/apps/docs/content/guides/auth/third-party/firebase-auth.mdx +++ b/apps/docs/content/guides/auth/third-party/firebase-auth.mdx @@ -4,19 +4,13 @@ title: 'Firebase Auth' subtitle: 'Use Firebase Auth with your Supabase project' --- - - -Creating a Firebase Auth third-party integration with your Supabase project is currently limited to a private alpha release. We are still trying to improve the developer experience around securing your project. [Register your interest in Third-Party Auth with Firebase](https://forms.supabase.com/third-party-auth-with-firebase) and the team will reach out to you. - - - Firebase Auth can be used as a third-party authentication provider alongside Supabase Auth, or standalone, with your Supabase project. ## Getting started 1. First you need to add an integration to connect your Supabase project with your Firebase project. You will need to get the Project ID in the [Firebase Console](https://console.firebase.google.com/u/0/project/_/settings/general). 2. Add a new Third-party Auth integration in your project's [Authentication settings](/dashboard/project/_/settings/auth). -3. If you are using Third Party Auth in local development, create and attach restrictive RLS policies to all tables in your public schema, Storage and Realtime to prevent unauthorized access from unrelated Firebase projects. +3. If you are using Third Party Auth when self hosting, create and attach restrictive RLS policies to all tables in your public schema, Storage and Realtime to **prevent unauthorized access from unrelated Firebase projects**. 4. Assign the `role: 'authenticated'` [custom user claim](https://firebase.google.com/docs/auth/admin/custom-claims) to all your users. 5. Finally set up the Supabase client in your application. @@ -33,7 +27,7 @@ import { createClient } from '@supabase/supabase-js' const supabase = createClient('https://.supabase.co', 'SUPABASE_ANON_KEY', { accessToken: async () => { - ;(await firebase.auth().currentUser?.getIdToken(/* forceRefresh */ false)) ?? null + return await firebase.auth().currentUser?.getIdToken(/* forceRefresh */ false)) ?? null }, }) ``` @@ -42,6 +36,26 @@ Make sure the all users in your application have the `role: 'authenticated'` [cu + + +Creating a client for the Web is as easy as passing the `accessToken` async function. This function should [return the Firebase Auth JWT of the current user](https://firebase.google.com/docs/auth/admin/verify-id-tokens) (or null if no such user) is found. + +```dart +await Supabase.initialize( + url: supabaseUrl, + anonKey: supabaseKey, + debug: false, + accessToken: () async { + final token = await FirebaseAuth.instance.currentUser?.getIdToken(); + return token; + }, +); +``` + +Make sure the all users in your application have the `role: 'authenticated'` [custom claim](https://firebase.google.com/docs/auth/admin/custom-claims) set. If you're using the `onCreate` Cloud Function to add this custom claim to newly signed up users, you will need to call `getIdToken(/* forceRefresh */ true)` immediately after sign up as the `onCreate` function does not run synchronously. + + + ```swift @@ -123,19 +137,21 @@ enabled = true project_id = "" ``` -## Adding an extra layer of security to your project's RLS policies +## Adding an extra layer of security to your project's RLS policies (self-hosting only) - + -While Third-Party Auth with Firebase is in a private alpha, it is strongly recommended to follow this guide closely. Once it becomes generally available this method would not be necessary to secure your project against unauthorized access from other Firebase projects. +**Please follow this section carefully to prevent unauthorized access to your project's data when self-hosting.** -You should also follow this best practice if self-hosting. +When using the Supabase hosted platform, following this step is optional. -Firebase Auth uses a single set of JWT signing keys for all projects. This means that JWTs issued from an unrelated Firebase project to yours could access data in your Supabase project. To guard against this, creating and maintaining the following RLS policies for **all of your tables in the `public` schema** is very important. You should also attach this policy to [Storage](/docs/guides/storage/security/access-control) buckets or [Realtime](/docs/guides/realtime/authorization) channels. +Firebase Auth uses a single set of JWT signing keys for all projects. This means that JWTs issued from an unrelated Firebase project to yours could access data in your Supabase project. -To achieve this we recommend using a [restrictive Postgres Row-Level Security policy](https://www.postgresql.org/docs/current/sql-createpolicy.html). +When using the Supabase hosted platform, JWTs coming from Firebase project IDs you have not registered will be rejected before they reach your database. When self-hosting implementing this mechanism is your responsibility. An easy way to guard against this is to create and maintain the following RLS policies for **all of your tables in the `public` schema**. You should also attach this policy to [Storage](/docs/guides/storage/security/access-control) buckets or [Realtime](/docs/guides/realtime/authorization) channels. + +It's recommended you use a [restrictive Postgres Row-Level Security policy](https://www.postgresql.org/docs/current/sql-createpolicy.html). Restrictive RLS policies differ from regular (or permissive) policies in that they use the `as restrictive` clause when being defined. They do not grant permissions, but rather restrict any existing or future permissions. They're great for cases like this where the technical limitations of Firebase Auth remain separate from your app's logic. diff --git a/apps/docs/content/guides/auth/third-party/overview.mdx b/apps/docs/content/guides/auth/third-party/overview.mdx index 041be2090b4..792858dbf58 100644 --- a/apps/docs/content/guides/auth/third-party/overview.mdx +++ b/apps/docs/content/guides/auth/third-party/overview.mdx @@ -12,7 +12,7 @@ Supabase has first-class support for these third-party authentication providers: You can use these providers alongside Supabase Auth, or on their own, to access the [Data API (REST and GraphQL)](/docs/guides/database), [Storage](/docs/guides/storage), [Realtime](/docs/guides/storage) and [Functions](/docs/guides/functions) from your existing apps. -If you already have production apps using one of these authentication providers, and would like to use a Supabase feature, you no longer need to migrate your users to Supabase Auth or use work-arounds like translating JWTs into the Supabase Auth format and using your project's signing secret. +If you already have production apps using one of these authentication providers, and would like to use a Supabase feature, you no longer need to migrate your users to Supabase Auth or use workarounds like translating JWTs into the Supabase Auth format and using your project's signing secret. ## How does it work? @@ -27,3 +27,9 @@ There are some limitations you should be aware of when using third-party authent 1. The third-party provider must use asymmetrically signed JWTs (exposed as an OIDC Issuer Discovery URL by the third-party authentication provider). Using symmetrically signed JWTs is not possible at this time. 2. The JWT signing keys from the third-party provider are stored in the configuration of your project, and are checked for changes periodically. If you are rotating your keys (when supported) allow up to 30 minutes for the change to be picked up. 3. It is not possible to disable Supabase Auth at this time. + +## Pricing + + + +For a detailed breakdown of how charges are calculated, refer to [Manage Monthly Active Third-Party Users usage](/docs/guides/platform/manage-your-usage/monthly-active-users-third-party). diff --git a/apps/docs/content/guides/auth/users.mdx b/apps/docs/content/guides/auth/users.mdx index 98786de022f..2b537566047 100644 --- a/apps/docs/content/guides/auth/users.mdx +++ b/apps/docs/content/guides/auth/users.mdx @@ -50,7 +50,7 @@ An identity describes the authentication method that a user can use to sign in. -A user with an email or phone identity will be able to sign in with either a password or passwordless method (e.g. use a one-time password (OTP) or magiclink). By default, a user with an unverified email or phone number will not be able to sign in. +A user with an email or phone identity will be able to sign in with either a password or passwordless method (e.g. use a one-time password (OTP) or magic link). By default, a user with an unverified email or phone number will not be able to sign in. diff --git a/apps/docs/content/guides/cli/getting-started.mdx b/apps/docs/content/guides/cli/getting-started.mdx deleted file mode 100644 index 7ad87f57e14..00000000000 --- a/apps/docs/content/guides/cli/getting-started.mdx +++ /dev/null @@ -1,222 +0,0 @@ ---- -title: 'Supabase CLI' -description: 'The Supabase CLI provides tools to develop your project locally and deploy to the Supabase Platform.' -subtitle: 'The Supabase CLI provides tools to develop your project locally and deploy to the Supabase Platform.' ---- - -You can use the Supabase CLI to run the entire Supabase stack locally on your machine, simply by running `supabase init` (to create a new local project) and then `supabase start`. - -The Supabase CLI provides tools to develop your project locally, deploy to the Supabase Platform, handle database migrations, and generate types directly from your database schema. - -## Installing the Supabase CLI - - - - -Install the CLI with [Homebrew](https://brew.sh): - -```sh -brew install supabase/tap/supabase -``` - - - - -Install the CLI with [Scoop](https://scoop.sh): - -```powershell -scoop bucket add supabase https://github.com/supabase/scoop-bucket.git -scoop install supabase -``` - - - - -The CLI is available through [Homebrew](https://brew.sh) and Linux packages. - -#### Homebrew - -```sh -brew install supabase/tap/supabase -``` - -#### Linux packages - -Linux packages are provided in [Releases](https://github.com/supabase/cli/releases). -To install, download the `.apk`/`.deb`/`.rpm` file depending on your package manager -and run one of the following: - -- `sudo apk add --allow-untrusted <...>.apk` -- `sudo dpkg -i <...>.deb` -- `sudo rpm -i <...>.rpm` - - - - -Run the CLI by prefixing each command with `npx`: - -```sh -npx supabase -``` - -Or with `bunx`: - -```sh -bunx supabase -``` - -You can also install the CLI as dev dependency via [npm](https://www.npmjs.com/package/supabase): - -```sh -npm install supabase --save-dev -``` - - - - -## Updating the Supabase CLI - -When a new [version](https://github.com/supabase/cli/releases) is released, you can update the CLI using the same methods. - - - - -```sh -brew upgrade supabase -``` - - - - -```powershell -scoop update supabase -``` - - - - -```sh -brew upgrade supabase -``` - - - - -If you have installed the CLI as dev dependency via [npm](https://www.npmjs.com/package/supabase), you can update it with: - -```sh -npm update supabase --save-dev -``` - - - - -If you have any Supabase containers running locally, stop them and delete their data volumes before proceeding with the upgrade. This ensures that Supabase managed services can apply new migrations on a clean state of the local database. - - - -Remember to save any local schema and data changes before stopping because the `--no-backup` flag will delete them. - -```sh -supabase db diff my_schema -supabase db dump --local --data-only > supabase/seed.sql -``` - - - -```sh -supabase stop --no-backup -supabase start -``` - -## Running Supabase locally - -The Supabase CLI uses Docker containers to manage the local development stack. To get started, - -- Install [Docker Desktop](https://docs.docker.com/desktop) and apply the following configurations - - - - -Docker settings on Mac: Select Integrated, Virtualization Framework, and osxfs - - - - -Docker settings on Windows: Select Integrated, Expose Daemon, WSL2, and Add to /etc/hosts file. - - - - -Inside the folder where you want to create your project, run: - -```bash -supabase init -``` - -This will create a new `supabase` folder. It's safe to commit this folder to your version control system. - -Now, to start the Supabase stack, run: - -```bash -supabase start -``` - -This takes time on your first run because the CLI needs to download the Docker images to your local machine. The CLI includes the entire Supabase toolset, and a few additional images that are useful for local development (like a local SMTP server and a database diff tool). - -The local development environment includes Supabase Studio, a graphical interface for working with your database, running by default on [localhost:54323](http://localhost:54323). - -![Local Studio](/docs/img/guides/cli/local-studio.png) - -## Stopping local services - -When you are finished working on your Supabase project, you can stop the stack with: - -```bash -supabase stop -``` - -## Full command reference - -The CLI provides a number of commands to help you develop your project locally and deploy to the Supabase Platform. You can find all commands inside the [CLI Reference](/docs/reference/cli/introduction) docs, including: - -- [Project](/docs/reference/cli/supabase-projects) and [Organization](/docs/reference/cli/supabase-orgs) management -- [Database management](/docs/reference/cli/supabase-db) -- [Database migrations](/docs/reference/cli/supabase-migration) and [Database Branching](/docs/reference/cli/supabase-branches) -- [Database debugging tools](/docs/reference/cli/supabase-inspect-db-calls) -- [Edge Function management](/docs/reference/cli/supabase-functions) -- [Auth management](/docs/reference/cli/supabase-sso) -- [Types Generators](/docs/reference/cli/supabase-gen) -- [Testing](/docs/reference/cli/supabase-test) diff --git a/apps/docs/content/guides/cron.mdx b/apps/docs/content/guides/cron.mdx new file mode 100644 index 00000000000..84182b0dd14 --- /dev/null +++ b/apps/docs/content/guides/cron.mdx @@ -0,0 +1,34 @@ +--- +title: 'Cron' +subtitle: 'Schedule Recurring Jobs with Cron Syntax in Postgres' +--- + +Supabase Cron is a Postgres Module that simplifies scheduling recurring Jobs with cron syntax and monitoring Job runs inside Postgres. + +Cron Jobs can be created via SQL or the Cron interface inside of Supabase Dashboard and can run anywhere from every second to once a year depending on your use case. + +Every Job can run SQL snippets or database functions with zero network latency or make an HTTP request, such as invoking a Supabase Edge Function, with ease. + + + +For best performance, we recommend no more than 8 Jobs run concurrently. Each Job should run no more than 10 minutes. + + + +## How does Cron work? + +Under the hood, Supabase Cron uses the [`pg_cron`](https://github.com/citusdata/pg_cron) Postgres database extension which is the scheduling and execution engine for your Jobs. + + + +`pg_cron` is not fully supported on Fly Postgres. Learn more about [Fly Postgres limitations](/docs/guides/platform/fly-postgres#limitations). + + + +The extension creates a `cron` schema in your database and all Jobs are stored on the `cron.job` table. Every Job's run and its status is recorded on the `cron.job_run_details` table. + +The Supabase Dashboard provides and interface for you to schedule Jobs and monitor Job runs. You can also do the same with SQL. + +## Resources + +- [`pg_cron` GitHub Repository](https://github.com/citusdata/pg_cron) diff --git a/apps/docs/content/guides/cron/install.mdx b/apps/docs/content/guides/cron/install.mdx new file mode 100644 index 00000000000..478bf9eec70 --- /dev/null +++ b/apps/docs/content/guides/cron/install.mdx @@ -0,0 +1,44 @@ +--- +title: 'Install' +--- + +Install the Supabase Cron Postgres Module to begin scheduling recurring Jobs. + + + + +1. Go to the [Cron Postgres Module](/dashboard/project/_/integrations/cron/overview) under Integrations in the Dashboard. +2. Enable the `pg_cron` extension. + + + + +```sql +create extension pg_cron with schema pg_catalog; + +grant usage on schema cron to postgres; +grant all privileges on all tables in schema cron to postgres; +``` + + + + +## Uninstall + +Uninstall Supabase Cron by disabling the `pg_cron` extension: + +```sql +drop extension if exists pg_cron; +``` + + + +Disabling the `pg_cron` extension will permanently delete all Jobs. + + diff --git a/apps/docs/content/guides/cron/quickstart.mdx b/apps/docs/content/guides/cron/quickstart.mdx new file mode 100644 index 00000000000..0ea18729a6a --- /dev/null +++ b/apps/docs/content/guides/cron/quickstart.mdx @@ -0,0 +1,337 @@ +--- +title: 'Quickstart' +--- + + + +Job names are case sensitive and cannot be edited once created. + +Attempting to create a second Job with the same name (and case) will overwrite the first Job. + + + +## Schedule a job + + + + +1. Go to the [Jobs](/dashboard/project/_/integrations/cron/jobs) section to schedule your first Job. +2. Click on `Create job` button or navigate to the new Cron Job form [here](/dashboard/project/_/integrations/cron/jobs?dialog-shown=true). +3. Name your Cron Job. +4. Choose a schedule for your Job by inputting cron syntax (refer to the syntax chart in the form) or natural language. +5. Input SQL snippet or select a Database function, HTTP request, or Supabase Edge Function. + +Cron Create + + + + +```sql +-- Cron Job name cannot be edited +select cron.schedule('permanent-cron-job-name', '30 seconds', 'CALL do_something()'); +``` + + + + + +
      + + + ``` + ┌───────────── min (0 - 59) + │ ┌────────────── hour (0 - 23) + │ │ ┌─────────────── day of month (1 - 31) + │ │ │ ┌──────────────── month (1 - 12) + │ │ │ │ ┌───────────────── day of week (0 - 6) (0 to 6 are Sunday to + │ │ │ │ │ Saturday, or use names; 7 is also Sunday) + │ │ │ │ │ + │ │ │ │ │ + * * * * * + ``` + + You can use [1-59] seconds (e.g. `30 seconds`) as the cron syntax to schedule sub-minute Jobs. + + + +
      +
      + + + +You can input seconds for your Job schedule interval as long as you're on Postgres version 15.1.1.61 or later. + + + +## Edit a job + + + + +1. Go to the [Jobs](/dashboard/project/_/integrations/cron/jobs) section and find the Job you'd like to edit. +2. Click on the three vertical dots menu on the right side of the Job and click `Edit cron job`. +3. Make your changes and then click `Save cron job`. + +Cron Edit + + + + +```sql +select cron.alter_job( + job_id := (select jobid from cron.job where jobname = 'permanent-cron-job-name'), + schedule := '*/5 * * * *' +); +``` + +Full options for the `cron.alter_job()` function are: + +```sql +cron.alter_job( + job_id bigint, + schedule text default null, + command text default null, + database text default null, + username text default null, + active boolean default null +) +``` + +It is also possible to modify a job by using the `cron.schedule()` function by inputting the same job name. This will replace the existing job via upsert. + + + + +## Activate/Deactivate a job + + + + +1. Go to the [Jobs](/dashboard/project/_/integrations/cron/jobs) section and find the Job you'd like to unschedule. +2. Toggle the `Active`/`Inactive` switch next to Job name. + +Cron Toggle + + + + +```sql +-- Activate Job +select cron.alter_job( + job_id := (select jobid from cron.job where jobname = 'permanent-cron-job-name'), + active := true +); + +-- Deactivate Job +select cron.alter_job( + job_id := (select jobid from cron.job where jobname = 'permanent-cron-job-name'), + active := false +); +``` + + + + +## Unschedule a job + + + + +1. Go to the [Jobs](/dashboard/project/_/integrations/cron/jobs) section and find the Job you'd like to delete. +2. Click on the three vertical dots menu on the right side of the Job and click `Delete cron job`. +3. Confirm deletion by entering the Job name. + +Cron Unschedule + + + + +```sql +select cron.unschedule('permanent-cron-job-name'); +``` + + + +Unscheduling a Job will permanently delete the Job from `cron.job` table but its run history remain in `cron.job_run_details` table. + + + + + + +## Inspecting job runs + + + + +1. Go to the [Jobs](/dashboard/project/_/integrations/cron/jobs) section and find the Job you want to see the runs of. +2. Click on the `History` button next to the Job name. + +Cron Job Runs + + + + +```sql +select + * +from cron.job_run_details +where jobid = (select jobid from cron.job where jobname = 'permanent-cron-job-name') +order by start_time desc +limit 10; +``` + + + +The records in the `cron.job_run_details` table are not cleaned up automatically. They are also not removed when jobs are unscheduled, which will take up disk space in your database. + + + + + + +## Examples + +### Delete data every week + +{/* */} + +Delete old data every Saturday at 3:30AM (GMT): + +{/* */} + +```sql +select cron.schedule ( + 'saturday-cleanup', -- name of the cron job + '30 3 * * 6', -- Saturday at 3:30AM (GMT) + $$ delete from events where event_time < now() - interval '1 week' $$ +); +``` + +### Run a vacuum every day + +{/* */} + +Vacuum every day at 3:00AM (GMT): + +{/* */} + +```sql +select cron.schedule('nightly-vacuum', '0 3 * * *', 'VACUUM'); +``` + +### Call a database function every 5 minutes + +Create a [`hello_world()`](/docs/guides/database/functions?language=sql#simple-functions) database function and then call it every 5 minutes: + +```sql +select cron.schedule('call-db-function', '*/5 * * * *', 'SELECT hello_world()'); +``` + +### Call a database stored procedure + +To use a stored procedure, you can call it like this: + +```sql +select cron.schedule('call-db-procedure', '*/5 * * * *', 'CALL my_procedure()'); +``` + +### Invoke Supabase Edge Function every 30 seconds + +Make a POST request to a Supabase Edge Function every 30 seconds: + +```sql +select + cron.schedule( + 'invoke-function-every-half-minute', + '30 seconds', + $$ + select + net.http_post( + url:='https://project-ref.supabase.co/functions/v1/function-name', + headers:=jsonb_build_object('Content-Type','application/json', 'Authorization', 'Bearer ' || 'YOUR_ANON_KEY'), + body:=jsonb_build_object('time', now() ), + timeout_milliseconds:=5000 + ) as request_id; + $$ + ); +``` + + + +This requires the [`pg_net` extension](/docs/guides/database/extensions/pg_net) to be enabled. + + + +## Caution: Scheduling system maintenance + +Be extremely careful when setting up Jobs for system maintenance tasks as they can have unintended consequences. + +For instance, scheduling a command to terminate idle connections with `pg_terminate_backend(pid)` can disrupt critical background processes like nightly backups. Often, there is an existing Postgres setting, such as `idle_session_timeout`, that can perform these common maintenance tasks without the risk. + +Reach out to [Supabase Support](https://supabase.com/support) if you're unsure if that applies to your use case. diff --git a/apps/docs/content/guides/database/arrays.mdx b/apps/docs/content/guides/database/arrays.mdx index a3b87c9248d..2fc92f3f019 100644 --- a/apps/docs/content/guides/database/arrays.mdx +++ b/apps/docs/content/guides/database/arrays.mdx @@ -132,9 +132,11 @@ supabase.from_('arraytest').insert( You should see: +``` | id | textarray | | --- | ----------------------- | | 1 | ["Harry","Larry","Moe"] | +```
      @@ -145,9 +147,11 @@ select * from arraytest; You should see: +``` | id | textarray | | --- | ----------------------- | | 1 | ["Harry","Larry","Moe"] | +``` @@ -173,9 +177,11 @@ SELECT textarray[1], array_length(textarray, 1) FROM arraytest; returns: +``` | textarray | array_length | | --------- | ------------ | | Harry | 3 | +``` diff --git a/apps/docs/content/guides/database/beekeeper-studio.mdx b/apps/docs/content/guides/database/beekeeper-studio.mdx new file mode 100644 index 00000000000..59145196cb7 --- /dev/null +++ b/apps/docs/content/guides/database/beekeeper-studio.mdx @@ -0,0 +1,81 @@ +--- +id: 'Beekeeper Studio' +title: 'Connecting with Beekeeper Studio' +breadcrumb: 'GUI Quickstarts' +hideToc: true +--- + +[`Beekeeper Studio Community`](https://www.beekeeperstudio.io/get-community) is a free GUI tool for interacting with databases. + + + + + + + + In Beekeeper, create a new Postgres connection. + + + + + ![Postgres connection](/docs/img/guides/database/connecting-to-postgres/beekeeper-studio/new-connection.png) + + + + + + + + + + Get your connection credentials from the [`Database Settings`](https://supabase.com/dashboard/project/_/settings/database). You will need: + - host + - username + - password + - port + + + + + + Add your credentials to Beekeeper's connection form + + ![Credentials](/docs/img/guides/database/connecting-to-postgres/beekeeper-studio/beekeeper-credentials.png) + + + + + + + + + + Download your SSL certificate from the Dashboard's [`Database Settings`](https://supabase.com/dashboard/project/_/settings/database) + ![SSL](/docs/img/guides/database/connecting-to-postgres/beekeeper-studio/certificate.png) + + + + + Add your SSL to the connection form + ![SSL](/docs/img/guides/database/connecting-to-postgres/beekeeper-studio/certificate-beekeeper.png) + + + + + + + + + + Test your connection and then connect + + + + + ![SSL](/docs/img/guides/database/connecting-to-postgres/beekeeper-studio/connect.png) + + + + + + diff --git a/apps/docs/content/guides/database/connecting-to-postgres.mdx b/apps/docs/content/guides/database/connecting-to-postgres.mdx index 09ede71962c..7f88e92a951 100644 --- a/apps/docs/content/guides/database/connecting-to-postgres.mdx +++ b/apps/docs/content/guides/database/connecting-to-postgres.mdx @@ -1,89 +1,139 @@ --- -id: 'connecting-to-postgres' -title: 'Connecting to your database' -description: 'Explore the options for connecting to your Postgres database.' -subtitle: 'Explore the options for connecting to your Postgres database.' +title: 'Connect to your database' +description: 'Connect to Postgres from your frontend, backend, or serverless environment' +subtitle: 'Connect to Postgres from your frontend, backend, or serverless environment' --- -Supabase provides several options for programmatically connecting to your Postgres database: +## Quick summary -1. The Data APIs -1. Using one of the many [Client Libraries](/docs/reference) -1. Direct connections using the built-in Postgres connection system -1. Connection pooling for scalable connections +How you connect to your database depends on where you're connecting from: -## Data APIs +- For frontend applications, use the [Data API](#data-apis-and-client-libraries) +- For Postgres clients, use a connection string + - For single sessions (for example, database GUIs) or Postgres native commands (for example, using client applications like [pg_dump](https://www.postgresql.org/docs/current/app-pgdump.html) or specifying connections for [replication](/docs/guides/database/postgres/setup-replication-external)) use the [direct connection string](#direct-connection) if your environment supports IPv6 + - For persistent clients, and support for both IPv4 and IPv6, use [Supavisor session mode](#supavisor-session-mode) + - For temporary clients (for example, serverless or edge functions) use [Supavisor transaction mode](#supavisor-transaction-mode) -Supabase's Data APIs are the easiest way to get started if you are managing data (fetching, inserting, updating). We provide several types of API to suit your preferences: +## Quickstarts -- [REST](/docs/guides/api): interact with your database through a REST interface. -- [GraphQL](/docs/guides/graphql/api): interact with your database through a GraphQL interface. -- [Realtime](/docs/guides/realtime#realtime-api): listen to database changes over websockets. +
      + + {(data) => + data.items?.map((quickstart) => ( + + + + )) + } + + + {(data) => + data.items?.map((quickstart) => ( + + + + )) + } + +
      -## Client libraries +## Data APIs and client libraries -Supabase offers [client libraries](/docs/reference) for popular programming languages. These libraries provide a convenient wrapper around the Data APIs, making it easier to interact with your database using your preferred language. +The Data APIs allow you to interact with your database using REST or GraphQL requests. You can use these APIs to fetch and insert data from the frontend, as long as you have [RLS](/docs/guides/database/postgres/row-level-security) enabled. -## Connecting to external libraries and tools +- [REST](/docs/guides/api) +- [GraphQL](/docs/guides/graphql/api) -Beyond the [Supabase client-libraries](/docs/guides/api/rest/client-libs), Supabase provides three connection strings for direct database access, compatible with all postgres libraries, such as [Prisma](/docs/guides/database/prisma) and [Drizzle](/docs/guides/database/drizzle), as well as tools like [PSQL](/docs/guides/database/psql) and [pgAdmin](/docs/guides/database/pgadmin). +For convenience, you can also use the Supabase client libraries, which wrap the Data APIs with a developer-friendly interface and automatically handle authentication: -You can find these connection strings in your [Database Settings](https://supabase.com/dashboard/project/_/settings/database). +- [JavaScript](/docs/reference/javascript) +- [Flutter](/docs/reference/dart) +- [Swift](/docs/reference/swift) +- [Python](/docs/reference/python) +- [C#](/docs/reference/csharp) +- [Kotlin](/docs/reference/kotlin) -### Direct connections: +## Direct connection -It connects directly to your Postgres instance. It is ideal for persistent servers, such as virtual machines (VMs) and long-lasting containers. Examples include AWS EC2 machines, Fly.io VMs, and DigitalOcean Droplets. +The direct connection string connects directly to your Postgres instance. It is ideal for persistent servers, such as virtual machines (VMs) and long-lasting containers. Examples include AWS EC2 machines, Fly.io VMs, and DigitalOcean Droplets. -Direct connections use IPv6 by default. If your environment doesn't support IPv6 or you're unsure but still want to use a direct connection, check out the [networking guide](/docs/guides/platform/ipv4-address#supabase-and-ipv6-compatibility) for more info. +Direct connections use IPv6 by default. If your environment doesn't support IPv6, use [Supavisor session mode](#supavisor-session-mode) or get the [IPv4 add-on](/docs/guides/platform/ipv4-address). -```md -// Example String +The connection string looks like this: + +``` postgresql://postgres:[YOUR-PASSWORD]@db.apbkobhfnmcqqzqeeqss.supabase.co:5432/postgres ``` -You can find the direct connection string in the [Database settings](https://supabase.com/dashboard/project/_/settings/database) inside the dashboard: +Get your project's direct string from the [Database Settings](/dashboard/project/_/settings/database) page: 1. Go to the `Settings` section. 2. Click `Database`. -3. Under `Connection string`, make sure `Use connection pooling` is unchecked. Copy the URI. +3. Under `Connection string`, make sure `Display connection pooler` is unchecked. Copy the URI. -### Supavisor session mode (port 5432): +## Supavisor session mode -In Session mode, Supavisor acts as an IPv4 proxy. It allows developers in IPv4-only environments to access a direct-connection experience without needing the IPv4 add-on. +The session mode connection string connects to your Postgres instance via a proxy. This is ideal for persistent servers when IPv6 is not supported. -```md -// Example String +The connection string looks like this: + +``` postgres://postgres.apbkobhfnmcqqzqeeqss:[YOUR-PASSWORD]@aws-0-ca-central-1.pooler.supabase.com:5432/postgres ``` -### Transaction mode (port 6543): +Get your project's session mode string from the [Database Settings](/dashboard/project/_/settings/database) page: + +1. Go to the `Settings` section. +2. Click `Database`. +3. Under `Connection string`, make sure `Display connection pooler` is checked and `Session mode` is selected. Copy the URI. + +## Supavisor transaction mode + +The transaction mode connection string connects to your Postgres instance via a proxy which serves as a connection pooler. This is ideal for serverless or edge functions, which require many transient connections. -Transaction mode does not support [prepared statements](https://postgresql.org/docs/current/sql-prepare.html). To avoid errors, [turn off prepared statements](https://github.com/orgs/supabase/discussions/28239) for your specific connection library. +Transaction mode does not support [prepared statements](https://postgresql.org/docs/current/sql-prepare.html). To avoid errors, [turn off prepared statements](https://github.com/orgs/supabase/discussions/28239) for your connection library. -Supavisor in transaction mode forms hot connections with the database and only allows clients to access them when a query is pending. It has amazing scaling capacity, and is best used with temporary servers, such as serverless/Edge Functions or auto-scaling servers. +The connection string looks like this: -```md -// Example String +``` postgres://postgres.apbkobhfnmcqqzqeeqss:[YOUR-PASSWORD]@aws-0-ca-central-1.pooler.supabase.com:6543/postgres ``` -## Connection pooling in-depth +Get your project's transaction mode string from the [Database Settings](/dashboard/project/_/settings/database) page: -### Application side poolers +1. Go to the `Settings` section. +2. Click `Database`. +3. Under `Connection string`, make sure `Display connection pooler` is checked and `Transaction mode` is selected. Copy the URI. + +## More about connection pooling + +Connection pooling improves database performance by reusing existing connections between queries. This reduces the overhead of establishing connections and improves scalability. + +You can use an application-side pooler or a server-side pooler (Supabase automatically provides one called Supavisor), depending on whether your backend is persistent or serverless. + +### Application-side poolers Application-side poolers are built into connection libraries and API servers, such as Prisma, SQLAlchemy, and PostgREST. They maintain several active connections with Postgres or a server-side pooler, reducing the overhead of establishing connections between queries. When deploying to static architecture, such as long-standing containers or VMs, application-side poolers are satisfactory on their own. ### Serverside poolers -Postgres connections are like WebSocket, once established, they are preserved until the client (application server) disconnects. A server might only make a single 10 ms query, but needlessly reserve its database connection for seconds or longer. +Postgres connections are like a WebSocket. Once established, they are preserved until the client (application server) disconnects. A server might only make a single 10 ms query, but needlessly reserve its database connection for seconds or longer. Serverside-poolers, such as Supabase's [Supavisor](https://github.com/supabase/supavisor) in transaction mode, sit between clients and the database and can be thought of as load balancers for Postgres connections. @@ -108,34 +158,6 @@ You can obtain your connection info and Server root certificate from your applic ![Connection Info and Certificate.](/docs/img/database/database-settings-ssl.png) -## Choosing a connection method - -- **Data APIs:** Best for operations supported by the Data APIs. -- **Supavisor Pooler:** - - **Transaction mode (port 6543):** Best for serverless/edge functions and rapidly auto-scaling servers - - **Session mode (port 5432):** For long-lived servers, such as virtual machines (VM) and enduring containers (if IPv6 is unsupported) -- **Direct Connections:** For long-lived servers, such as VMs and enduring containers (if IPv6 is supported or the IPv4 Add-On is enabled). - -## Quickstart connection guides - - - -If you'd like us to add a new quickstart guide, share your [feedback](https://supabase.com/dashboard/support/new), and we'll try to add it going forwards. - - - -### External libraries - -- [Prisma](/docs/guides/database/prisma) -- [Drizzle](/docs/guides/database/drizzle) -- [Postgres.js](/docs/guides/database/drizzle) - -### External tools - -- [pgAdmin](/docs/guides/database/pgadmin) -- [PSQL](/docs/guides/database/psql) -- [DBeaver](/docs/guides/database/dbeaver) - ## Resources - [Connection management](/docs/guides/database/connection-management) diff --git a/apps/docs/content/guides/database/connection-management.mdx b/apps/docs/content/guides/database/connection-management.mdx index 72c5df291c9..4a13b4aa4e5 100644 --- a/apps/docs/content/guides/database/connection-management.mdx +++ b/apps/docs/content/guides/database/connection-management.mdx @@ -17,7 +17,7 @@ You can change how many database connections Supavisor can manage by altering th The general rule is that if you are heavily using the PostgREST database API, you should be conscientious about raising your pool size past 40%. Otherwise, you can commit 80% to the pool. This leaves adequate room for the Authentication server and other utilities. -These numbers are generalizations and depends on other Supabase products that you use and the extent of their usage. The actual values depend on your peak connection usage. For instance, if you were only using 80 connections in a week period and your database max connections is set to 500, then realistically you could allocate the difference of 420 (minus a reasonable buffer) to service more demand. +These numbers are generalizations and depends on other Supabase products that you use and the extent of their usage. The actual values depend on your concurrent peak connection usage. For instance, if you were only using 80 connections in a week period and your database max connections is set to 500, then realistically you could allocate the difference of 420 (minus a reasonable buffer) to service more demand. ## Monitoring connections @@ -53,28 +53,27 @@ ON pg_stat_ssl.pid = pg_stat_activity.pid; Interpreting the query: -| Column | Description | -| ---------------- | ------------------------------------------------- | -| connection_id | connection id | -| ssl | Indicates if SSL is in use | -| database | Name of the connected database (usually postgres) | -| usename | Role of the connected user | -| application_name | Name of the connecting application | -| client_addr | IP address of the connecting server | -| query | Last query executed by the connection | -| query_start | Time when the last query was executed | -| state | Querying state: active or idle | -| backend_start | Timestamp of the connection's establishment | +| Column | Description | +| ------------------ | --------------------------------------------------- | +| `connection_id` | connection id | +| `ssl` | Indicates if SSL is in use | +| `database` | Name of the connected database (usually `postgres`) | +| `usename` | Role of the connected user | +| `application_name` | Name of the connecting application | +| `client_addr` | IP address of the connecting server | +| `query` | Last query executed by the connection | +| `query_start` | Time when the last query was executed | +| `state` | Querying state: active or idle | +| `backend_start` | Timestamp of the connection's establishment | -The usename can be used to identify the source: +The username can be used to identify the source: | Role | API/Tool | | ---------------------------- | ------------------------------------------------------------------------- | -| supabase_admin | Used by Supabase to configure projects and for monitoring | -| authenticator | Data API (PostgREST) | -| supabase_auth_admin | Auth | -| supabase_storage_admin | Storage | -| supabase_realtime_admin | Realtime | -| supabase_replication_admin | Synchronizes Read Replicas | -| postgres | Supabase Dashboard and External Tools (e.g., Prisma, SQLAlchemy, PSQL...) | +| `supabase_admin` | Used by Supabase for monitoring and by Realtime | +| `authenticator` | Data API (PostgREST) | +| `supabase_auth_admin` | Auth | +| `supabase_storage_admin` | Storage | +| `supabase_replication_admin` | Synchronizes Read Replicas | +| `postgres` | Supabase Dashboard and External Tools (e.g., Prisma, SQLAlchemy, PSQL...) | | Custom roles defined by user | External Tools (e.g., Prisma, SQLAlchemy, PSQL...) | diff --git a/apps/docs/content/guides/database/custom-postgres-config.mdx b/apps/docs/content/guides/database/custom-postgres-config.mdx index a77662f838b..a4d84b971e1 100644 --- a/apps/docs/content/guides/database/custom-postgres-config.mdx +++ b/apps/docs/content/guides/database/custom-postgres-config.mdx @@ -53,13 +53,15 @@ Some settings can only be modified by a superuser. Supabase pre-enables the [`su | `auto_explain.log_min_duration` | Logs query plans taking longer than this duration. | | `auto_explain.log_nested_statements` | Log nested statements' plans. | | `log_min_messages` | Minimum severity level of messages to log. | +| `pg_net.ttl` | Sets how long the [pg_net extension](/docs/guides/database/extensions/pg_net) saves responses | +| `pg_net.batch_size` | Sets how many requests the [pg_net extension](/docs/guides/database/extensions/pg_net) can make per second | | `pgaudit.*` | Configures the [PGAudit extension](https://supabase.com/docs/guides/database/extensions/pgaudit). The `log_parameter` is still restricted to protect secrets | | `pgrst.*` | [`PostgREST` settings](https://docs.postgrest.org/en/stable/references/configuration.html#db-aggregates-enabled) | | `plan_filter.*` | Configures the [pg_plan_filter extension](https://supabase.com/docs/guides/database/extensions/pg_plan_filter) | | `session_replication_role` | Sets the session's behavior for triggers and rewrite rules. | | `track_io_timing` | Collects timing statistics for database I/O activity. | -For example, to enable `log_nested_statements` for the postgres role, execute: +For example, to enable `log_nested_statements` for the `postgres` role, execute: ```sql alter role "postgres" set "auto_explain.log_nested_statements" to 'on'; @@ -85,6 +87,12 @@ CLI changes permanently overwrite default settings, so `reset all` and `set to d
      + + +In order to overwrite the default settings, you must have `Owner` or `Administrator` privileges within your organizations. + + + #### CLI supported parameters @@ -95,26 +103,29 @@ If a setting you need is not yet configurable, [share your use case with us](htt The following parameters are available for overrides: -1. [effective_cache_size](https://postgresqlco.nf/doc/en/param/effective_cache_size/) -1. [logical_decoding_work_mem](https://postgresqlco.nf/doc/en/param/logical_decoding_work_mem/) (CLI only) -1. [maintenance_work_mem](https://postgresqlco.nf/doc/en/param/maintenance_work_mem/) -1. [max_connections](https://postgresqlco.nf/doc/en/param/max_connections/) (CLI only) -1. [max_locks_per_transaction](https://postgresqlco.nf/doc/en/param/max_locks_per_transaction/) (CLI only) -1. [max_parallel_maintenance_workers](https://postgresqlco.nf/doc/en/param/max_parallel_maintenance_workers/) -1. [max_parallel_workers_per_gather](https://postgresqlco.nf/doc/en/param/max_parallel_workers_per_gather/) -1. [max_parallel_workers](https://postgresqlco.nf/doc/en/param/max_parallel_workers/) -1. [max_replication_slots](https://postgresqlco.nf/doc/en/param/max_replication_slots/) (CLI only) -1. [max_slot_wal_keep_size](https://postgresqlco.nf/doc/en/param/max_slot_wal_keep_size/) (CLI only) -1. [max_standby_archive_delay](https://postgresqlco.nf/doc/en/param/max_standby_archive_delay) (CLI only) -1. [max_standby_streaming_delay](https://postgresqlco.nf/doc/en/param/max_standby_streaming_delay) (CLI only) -1. [max_wal_size](https://postgresqlco.nf/doc/en/param/max_wal_size) (CLI only) -1. [max_wal_senders](https://postgresqlco.nf/doc/en/param/max_wal_senders) (CLI only) -1. [max_worker_processes](https://postgresqlco.nf/doc/en/param/max_worker_processes)(https://postgresqlco.nf/doc/en/param/max_worker_processes/) (CLI only) -1. [session_replication_role](https://postgresqlco.nf/doc/en/param/session_replication_role)(https://postgresqlco.nf/doc/en/param/session_replication_role/) -1. [shared_buffers](https://postgresqlco.nf/doc/en/param/shared_buffers)(https://postgresqlco.nf/doc/en/param/shared_buffers/) (CLI only) -1. [wal_keep_size](https://postgresqlco.nf/doc/en/param/wal_keep_size) (CLI only) -1. [wal_sender_timeout](https://postgresqlco.nf/doc/en/param/wal_sender_timeout) (CLI only) -1. [work_mem](https://postgresqlco.nf/doc/en/param/work_mem/) +1. [effective_cache_size](https://www.postgresql.org/docs/current/runtime-config-query.html#GUC-EFFECTIVE-CACHE-SIZE) +1. [logical_decoding_work_mem](https://www.postgresql.org/docs/current/runtime-config-resource.html#GUC-LOGICAL-DECODING-WORK-MEM) (CLI only) +1. [maintenance_work_mem](https://www.postgresql.org/docs/current/runtime-config-resource.html#GUC-MAINTENANCE-WORK-MEM) +1. [max_connections](https://www.postgresql.org/docs/current/runtime-config-connection.html#GUC-MAX-CONNECTIONS) (CLI only) +1. [max_locks_per_transaction](https://www.postgresql.org/docs/current/runtime-config-locks.html#GUC-MAX-LOCKS-PER-TRANSACTION) (CLI only) +1. [max_parallel_maintenance_workers](https://www.postgresql.org/docs/current/runtime-config-resource.html#GUC-MAX-PARALLEL-MAINTENANCE-WORKERS) +1. [max_parallel_workers_per_gather](https://www.postgresql.org/docs/current/runtime-config-resource.html#GUC-MAX-PARALLEL-WORKERS-PER-GATHER) +1. [max_parallel_workers](https://www.postgresql.org/docs/current/runtime-config-resource.html#GUC-MAX-PARALLEL-WORKERS) +1. [max_replication_slots](https://www.postgresql.org/docs/current/runtime-config-replication.html#GUC-MAX-REPLICATION-SLOTS) (CLI only) +1. [max_slot_wal_keep_size](https://www.postgresql.org/docs/current/runtime-config-replication.html#GUC-MAX-SLOT-WAL-KEEP-SIZE) (CLI only) +1. [max_standby_archive_delay](https://www.postgresql.org/docs/current/runtime-config-replication.html#GUC-MAX-STANDBY-ARCHIVE-DELAY) (CLI only) +1. [max_standby_streaming_delay](https://www.postgresql.org/docs/current/runtime-config-replication.html#GUC-MAX-STANDBY-STREAMING-DELAY) (CLI only) +1. [max_wal_size](https://www.postgresql.org/docs/current/runtime-config-wal.html#GUC-MAX-WAL-SIZE) (CLI only) +1. [max_wal_senders](https://www.postgresql.org/docs/current/runtime-config-replication.html#GUC-MAX-WAL-SENDERS) (CLI only) +1. [max_worker_processes](https://www.postgresql.org/docs/current/runtime-config-resource.html#GUC-MAX-WORKER-PROCESSES) (CLI only) +1. [session_replication_role](https://www.postgresql.org/docs/current/runtime-config-client.html#GUC-SESSION-REPLICATION-ROLE) +1. [shared_buffers](https://www.postgresql.org/docs/current/runtime-config-resource.html#GUC-SHARED-BUFFERS) (CLI only) +1. [statement_timeout](https://www.postgresql.org/docs/current/runtime-config-client.html#GUC-STATEMENT-TIMEOUT) +1. [track_activity_query_size](https://www.postgresql.org/docs/current/runtime-config-statistics.html#GUC-TRACK-ACTIVITY-QUERY-SIZE) +1. [track_commit_timestamp](https://www.postgresql.org/docs/current/runtime-config-replication.html#GUC-TRACK-COMMIT-TIMESTAMP) +1. [wal_keep_size](https://www.postgresql.org/docs/current/runtime-config-replication.html#GUC-WAL-KEEP-SIZE) (CLI only) +1. [wal_sender_timeout](https://www.postgresql.org/docs/current/runtime-config-replication.html#GUC-WAL-SENDER-TIMEOUT) (CLI only) +1. [work_mem](https://www.postgresql.org/docs/current/runtime-config-resource.html#GUC-WORK-MEM) #### Managing Postgres configuration with the CLI @@ -140,6 +151,22 @@ postgres-config update --config max_parallel_workers=3 \ --replace-existing-overrides ``` +To delete specific configuration overrides, use the `postgres-config delete` command: + +```bash +supabase --experimental \ +--project-ref \ +postgres-config delete --config shared_buffers,work_mem +``` + +By default, this might restart the database after deleting the configurations. You can use the `--no-restart` flag to prevent the restart: + +```bash +supabase --experimental \ +--project-ref \ +postgres-config delete --config shared_buffers --no-restart +``` + ### Resetting to default config To reset a setting to its default value at the database level: @@ -160,6 +187,6 @@ alter role "" set "" to default; ### Considerations -1. Changes through the CLI must restart the database and will cause momentary disruption to existing database connections; in most cases this should not take more than a few seconds. +1. Changes through the CLI might restart the database causing momentary disruption to existing database connections; in most cases this should not take more than a few seconds. However, you can use the --no-restart flag to bypass the restart and keep the connections intact. Keep in mind that this depends on the specific configuration changes you're making. if the change requires a restart, using the --no-restart flag will prevent the restart but you won't see those changes take effect until a restart is manually triggered. 1. Custom Postgres Config will always override the default optimizations generated by Supabase. When changing compute add-ons, you should also review and update your custom Postgres Config to ensure they remain compatible and effective with the updated compute. 1. Some parameters (e.g. `wal_keep_size`) can increase disk utilization, triggering disk expansion, which in turn can lead to [increases in your bill](/docs/guides/platform/compute-add-ons#disk-io). diff --git a/apps/docs/content/guides/database/debugging-performance.mdx b/apps/docs/content/guides/database/debugging-performance.mdx index 7970377daff..2a7a64dbc52 100644 --- a/apps/docs/content/guides/database/debugging-performance.mdx +++ b/apps/docs/content/guides/database/debugging-performance.mdx @@ -14,7 +14,7 @@ subtitle: 'Debug slow-running queries using the Postgres execution planner.' {/* prettier-ignore */} ```sql -- enable explain -alter role authenticator +alter role authenticator set pgrst.db_plan_enabled to 'true'; -- reload the config @@ -28,28 +28,28 @@ To get the execution plan of a query, you can chain the `explain()` method to a {/* prettier-ignore */} ```ts const { data, error } = await supabase - .from('countries') + .from('instruments') .select() .explain() ``` ### Example data -To illustrate, consider the following setup of a `countries` table: +To illustrate, consider the following setup of a `instruments` table: {/* prettier-ignore */} ```sql -create table countries ( +create table instruments ( id int8 primary key, name text ); -insert into countries +insert into books (id, name) values - (1, 'Afghanistan'), - (2, 'Albania'), - (3, 'Algeria'); + (1, 'violin'), + (2, 'viola'), + (3, 'cello'); ``` ### Expected response @@ -60,7 +60,7 @@ The response would typically look like this: ```markdown Aggregate (cost=33.34..33.36 rows=1 width=112) -> Limit (cost=0.00..18.33 rows=1000 width=40) - -> Seq Scan on countries (cost=0.00..22.00 rows=1200 width=40) + -> Seq Scan on instruments (cost=0.00..22.00 rows=1200 width=40) ``` By default, the execution plan is returned in TEXT format. However, you can also retrieve it as JSON by specifying the `format` parameter. @@ -97,11 +97,11 @@ To disable the `explain()` method after use, execute the following SQL commands: {/* prettier-ignore */} ```sql -- disable explain -alter role authenticator +alter role authenticator set pgrst.db_plan_enabled to 'false'; -- if you used the above pre-request -alter role authenticator +alter role authenticator set pgrst.db_pre_request to ''; -- reload the config diff --git a/apps/docs/content/guides/database/extensions.mdx b/apps/docs/content/guides/database/extensions.mdx index d5fb2834e26..3bdbb3db71f 100644 --- a/apps/docs/content/guides/database/extensions.mdx +++ b/apps/docs/content/guides/database/extensions.mdx @@ -54,7 +54,7 @@ In addition to the pre-configured extensions, you can also install your own SQL -### Upgrade Extensions +### Upgrade extensions If a new version of an extension becomes available on Supabase, you need to initiate a software upgrade in the [Infrastructure Settings](https://supabase.com/dashboard/project/_/settings/infrastructure) to access it. Software upgrades can also be initiated by restarting your server in the [General Settings](https://supabase.com/dashboard/project/_/settings/general). diff --git a/apps/docs/content/guides/database/extensions/http.mdx b/apps/docs/content/guides/database/extensions/http.mdx index e3a1e510cd7..0ee06be4321 100644 --- a/apps/docs/content/guides/database/extensions/http.mdx +++ b/apps/docs/content/guides/database/extensions/http.mdx @@ -46,7 +46,7 @@ You can use the `http` extension to make these network requests from Postgres. 1. Go to the [Database](https://supabase.com/dashboard/project/_/database/tables) page in the Dashboard. 2. Click on **Extensions** in the sidebar. -3. Search for "http" and enable the extension. +3. Search for `http` and enable the extension. diff --git a/apps/docs/content/guides/database/extensions/index_advisor.mdx b/apps/docs/content/guides/database/extensions/index_advisor.mdx index a1cf0d29d5e..82ab1d1df98 100644 --- a/apps/docs/content/guides/database/extensions/index_advisor.mdx +++ b/apps/docs/content/guides/database/extensions/index_advisor.mdx @@ -13,7 +13,7 @@ Features: - Identifies tables/columns obfuscated by views - Skips duplicate indexes -`index_advisor` is accessible directly through Supabase Studio by navigating to the [Query Performance Report](/dashboard/project/_/database/query-performance) and selecting a query and then the "indexes" tab. +`index_advisor` is accessible directly through Supabase Studio by navigating to the [Query Performance Report](/dashboard/project/_/advisors/query-performance) and selecting a query and then the "indexes" tab. ![Supabase Studio index_advisor integration.](/docs/img/index_advisor_studio.png) diff --git a/apps/docs/content/guides/database/extensions/pg_cron.mdx b/apps/docs/content/guides/database/extensions/pg_cron.mdx index 22a0efe7b64..680a7680351 100644 --- a/apps/docs/content/guides/database/extensions/pg_cron.mdx +++ b/apps/docs/content/guides/database/extensions/pg_cron.mdx @@ -1,194 +1,7 @@ --- id: 'pg_cron' -title: 'pg_cron: Job Scheduling' -description: 'pgnet: a simple cron-based job scheduler for PostgreSQL that runs inside the database.' +title: 'pg_cron: Schedule Recurring Jobs with Cron Syntax in Postgres' +description: 'pg_cron: schedule recurring Jobs with cron syntax in Postgres.' --- -The `pg_cron` extension is a simple cron-based job scheduler for PostgreSQL that runs inside the database. - - - -pg_cron is not fully supported on Fly Postgres. Read more about this [Fly Postgres limitation here](/docs/guides/platform/fly-postgres#limitations). - - - -## Usage - -### Enable the extension - - - - -1. Go to the [Database](https://supabase.com/dashboard/project/_/database/tables) page in the Dashboard. -2. Click on **Extensions** in the sidebar. -3. Search for "pg_cron" and enable the extension. - - - - -```sql --- Example: enable the "pg_cron" extension -create extension pg_cron with schema extensions; - -grant usage on schema cron to postgres; -grant all privileges on all tables in schema cron to postgres; - --- Example: disable the "pg_cron" extension -drop extension if exists pg_cron; -``` - - - - -### Syntax - -The schedule uses the standard cron syntax, in which \* means "run every time period", and a specific number means "but only at this time": - -```bash - ┌───────────── min (0 - 59) - │ ┌────────────── hour (0 - 23) - │ │ ┌─────────────── day of month (1 - 31) - │ │ │ ┌──────────────── month (1 - 12) - │ │ │ │ ┌───────────────── day of week (0 - 6) (0 to 6 are Sunday to - │ │ │ │ │ Saturday, or use names; 7 is also Sunday) - │ │ │ │ │ - │ │ │ │ │ - * * * * * -``` - -You can use `[1-59] seconds` as the cron syntax to schedule sub-minute jobs. This is available on `pg_cron` v1.5.0+; upgrade your existing Supabase project to use this syntax. - -Head over to [crontab.guru](https://crontab.guru/) to validate your cron schedules. - -### Scheduling system maintenance - -Be extremely careful when setting up `pg_cron` jobs for system maintenance tasks as they can have unintended consequences. For instance, scheduling a command to terminate idle connections with `pg_terminate_backend(pid)` can disrupt critical background processes like nightly backups. Often, there is an existing Postgres setting e.g. `idle_session_timeout` that can perform these common maintenance tasks without the risk. - -Reach out to [Supabase Support](https://supabase.com/support) if you're unsure if that applies to your use case. - -## Examples - -### Delete data every week - -Delete old data every Saturday at 3:30am (GMT): - -```sql -select cron.schedule ( - 'saturday-cleanup', -- name of the cron job - '30 3 * * 6', -- Saturday at 3:30am (GMT) - $$ delete from events where event_time < now() - interval '1 week' $$ -); -``` - -### Run a vacuum every day - -Vacuum every day at 3:00am (GMT): - -```sql -select cron.schedule('nightly-vacuum', '0 3 * * *', 'VACUUM'); -``` - -### Call a database function every 5 minutes - -Create a [`hello_world()`](https://supabase.com/docs/guides/database/functions?language=sql#simple-functions) database function and then call it every 5 minutes: - -```sql -select cron.schedule('call-db-function', '*/5 * * * *', 'SELECT hello_world()'); -``` - -### Call a database stored procedure - -To use a stored procedure, you can call it like this: - -```sql -select cron.schedule('call-db-procedure', '*/5 * * * *', 'CALL my_procedure()'); -``` - -### Invoke Supabase Edge Function every 30 seconds - - - -This requires `pg_cron` v1.5.0+ and the [`pg_net` extension](/docs/guides/database/extensions/pgnet) to be enabled. - - - -Make a POST request to a Supabase Edge Function every 30 seconds: - -```sql -select - cron.schedule( - 'invoke-function-every-half-minute', - '30 seconds', - $$ - select - net.http_post( - url:='https://project-ref.supabase.co/functions/v1/function-name', - headers:=jsonb_build_object('Content-Type','application/json', 'Authorization', 'Bearer ' || 'YOUR_ANON_KEY'), - body:=jsonb_build_object('time', now() ), - timeout_milliseconds:=5000 - ) as request_id; - $$ - ); -``` - -### Edit a job - -Changes the frequency of a job called `'vacuum'` to once every 5 minutes. - -```sql -select cron.alter_job( - job_id := (select jobid from cron.job where jobname = 'vacuum'), - schedule := '*/5 * * * *' -); -``` - -Full options for the `cron.alter_job()` function are: - -```sql -cron.alter_job( - job_id bigint, - schedule text default null, - command text default null, - database text default null, - username text default null, - active boolean default null -) -``` - -It is also possible to modify a job by using the `cron.schedule()` function, with the same job name. This will replace the existing job, in the manner of an upsert. - -### Unschedule a job - -Unschedules a job called `'nightly-vacuum'` - -```sql -select cron.unschedule('nightly-vacuum'); -``` - -### Viewing previously ran jobs - -View the last ten jobs that have ran - -```sql -select - * -from cron.job_run_details -order by start_time desc -limit 10; -``` - - - -The records in cron.job_run_details are not cleaned automatically which will take up disk space in your database. - - - -## Resources - -- [`pg_cron` GitHub Repository](https://github.com/citusdata/pg_cron) +See the [Supabase Cron docs](/docs/guides/cron). diff --git a/apps/docs/content/guides/database/extensions/pg_hashids.mdx b/apps/docs/content/guides/database/extensions/pg_hashids.mdx index e53700420e1..07383d98c83 100644 --- a/apps/docs/content/guides/database/extensions/pg_hashids.mdx +++ b/apps/docs/content/guides/database/extensions/pg_hashids.mdx @@ -4,7 +4,7 @@ title: 'pg_hashids: Short UIDs' description: 'Generate Short UIDs from Numbers' --- -[pg_hashids](https://github.com/iCyberon/pg_hashids) provides a secure way to generate short, unique, non-sequential ids from numbers. The hashes are intended to be small, easy-to-remember identifiers that can be used to obfuscate data (optionally) with a password, alphabet, and salt. For example, you may wish to hide data like user IDs, order numbers, or tracking codes in favor of `pg_hashid`'s unique identifers. +[pg_hashids](https://github.com/iCyberon/pg_hashids) provides a secure way to generate short, unique, non-sequential ids from numbers. The hashes are intended to be small, easy-to-remember identifiers that can be used to obfuscate data (optionally) with a password, alphabet, and salt. For example, you may wish to hide data like user IDs, order numbers, or tracking codes in favor of `pg_hashid`'s unique identifiers. ## Enable the extension @@ -43,7 +43,7 @@ It's good practice to create the extension within a separate schema (like `exten ## Usage -Suppose we have a table that stores order information, and we want to give customers a unique identifer without exposing the sequential `id` column. To do this, we can use `pg_hashid`'s `id_encode` function. +Suppose we have a table that stores order information, and we want to give customers a unique identifier without exposing the sequential `id` column. To do this, we can use `pg_hashid`'s `id_encode` function. ```sql create table orders ( diff --git a/apps/docs/content/guides/database/extensions/pg_net.mdx b/apps/docs/content/guides/database/extensions/pg_net.mdx index 8556179afb4..bd477e4cdbf 100644 --- a/apps/docs/content/guides/database/extensions/pg_net.mdx +++ b/apps/docs/content/guides/database/extensions/pg_net.mdx @@ -279,11 +279,11 @@ begin ) as request_id; -- Optionally Log row data or other data for inspection in Supabase Dashboard's Postgres Logs - -- (https://supabase.com/dashboard/project/_/logs/postgres-logs) raise log 'Logging an entire row as JSON (%)', row_data_var; - -- return the row data to inspect + -- return row data to inspect return row_data_var; + -- Handle exceptions here if needed exception when others then @@ -309,13 +309,13 @@ order by "created" desc; ## Configuration - + -See the [self-hosting docs](/docs/guides/hosting/overview#configuration) for self-hosting instructions. +Supabase supports reconfiguring pg*net starting from v0.12.0+. For the latest release, initiate a Postgres upgrade in the [Infrastructure Settings](https://supabase.com/dashboard/project/*/settings/infrastructure). -The extension is configured to execute up to 200 requests per second reliably. The response messages are stored for only 6 hours to prevent needless buildup. The default behavior can be modified by rewriting config variables. +The extension is configured to reliably execute up to 200 requests per second. The response messages are stored for only 6 hours to prevent needless buildup. The default behavior can be modified by rewriting config variables. ### Get current settings @@ -329,18 +329,11 @@ where "name" like 'pg_net%'; ### Alter settings -Note, that doing `ALTER SYSTEM` requires SUPERUSER but on PostgreSQL >= 15, you can do: +Change variables: ```sql -grant alter system on parameter pg_net.ttl to ; -grant alter system on parameter pg_net.batch_size to ; -``` - -Then variables can be changed: - -```sql -alter system set pg_net.ttl to '1 hour' -alter system set pg_net.batch_size to 500; +alter role "postgres" set pg_net.ttl to '24 hours'; +alter role "postgres" set pg_net.batch_size to 500; ``` Then reload the settings and restart the `pg_net` background worker with: @@ -423,7 +416,7 @@ with "selected_table_rows" as ( -- Converts all the rows into a JSONB array jsonb_agg(to_jsonb(.*)) as JSON_payload from - -- Generally good practice to LIMIT the max amount of rows + -- good practice to LIMIT the max amount of rows ) select net.http_post( @@ -433,7 +426,7 @@ select FROM "selected_table_rows"; ``` -More examples can be seen in the [Extension's GitHub page](https://github.com/supabase/pg_net/) +More examples can be seen on the [Extension's GitHub page](https://github.com/supabase/pg_net/) ## Limitations @@ -442,7 +435,7 @@ More examples can be seen in the [Extension's GitHub page](https://github.com/su - Can only make POST requests with JSON data. No other data formats are supported - Intended to handle at most 200 requests per second. Increasing the rate can introduce instability - Does not have support for PATCH/PUT requests -- Can only work with one database at a time in PostgreSQL. It defaults to the postgres database. +- Can only work with one database at a time. It defaults to the `postgres` database. ## Resources diff --git a/apps/docs/content/guides/database/extensions/pg_partman.mdx b/apps/docs/content/guides/database/extensions/pg_partman.mdx deleted file mode 100644 index d907d55cdca..00000000000 --- a/apps/docs/content/guides/database/extensions/pg_partman.mdx +++ /dev/null @@ -1,100 +0,0 @@ ---- -id: 'pg_partman' -title: 'pg_partman: partition management' -description: 'Automated partition management' ---- - -[`pg_partman`](https://github.com/pgpartman/pg_partman) is a PostgreSQL extension that enables automated partition management. It removes the need to create and detach partitions for tables partitioned by time or an integer series. - -## Enable the extension - -To enable `pg_partman`, first enable the extension in your database inside a dedicated schema. - -{/* prettier-ignore */} -```sql -create schema partman; -create extension pg_partman schema partman; -``` - -## Setting up Partitioning - -pg_partman supports partitioning based on time (e.g., by day, month, or year) or by a sequential integer. Setup is performed using the `create_parent` function. - -For time-based partitioning: - -{/* prettier-ignore */} -```sql -select partman.create_parent('schema_name.table_name', 'partition_column', 'time', 'daily'); -``` - -or for integer-based partitioning: - -{/* prettier-ignore */} -```sql -select partman.create_parent('schema_name.table_name', 'partition_column', 'integer', '1000'); -``` - -## API - -- partman.create_parent: creates a parent table for partitioning -- partman.run_maintenance: ensures partitions are created and dropped as per the defined settings. This should be run periodically, such as via a cron job scheduled by pg_cron - -## Example - -Let’s set up a simple partitioning example using pg_partman with time-based partitions. - -{/* prettier-ignore */} -```sql --- Create the table -create table public.foo ( - id serial primary key, - data text, - created_at timestamp not null -); - --- Create the parent table -select - partman.create_parent( - 'public.foo', - 'created_at', - 'time', - 'daily' - ); - --- Configure the partitioned table -update - partman.part_config -set - infinite_time_partitions = true, - retention = '3 months', - retention_keep_table=true -where - parent_table = 'public.foo'; -``` - -For more information about available configuration, see the [pg_partman docs](https://github.com/pgpartman/pg_partman/blob/de93f4a104b8566087f4b9b2a0edf25893672da8/doc/pg_partman.md#configuration-tables). - -Following these statements, partitions for `public.foo` will be automatically managed as necessary when the `partman.run_maintenance()` function is called. - -## Running Maintenance - -It is important to call the `partman.run_maintenance()` procedure frequently to ensure partitions are created and detached according to the partitioned table's configuration. - -{/* prettier-ignore */} -```sql -call partman.run_maintenance_proc(); -``` - -To automate this task, consider using `pg_cron`. - -{/* prettier-ignore */} -```sql -create extension pg_cron; - -select - cron.schedule('@hourly', $$call partman.run_maintenance_proc()$$); -``` - -## Resources - -- Official [pg_partman documentation](https://github.com/pgpartman/pg_partman/blob/master/doc/pg_partman_howto.md) diff --git a/apps/docs/content/guides/database/extensions/pg_repack.mdx b/apps/docs/content/guides/database/extensions/pg_repack.mdx new file mode 100644 index 00000000000..b9416f2349a --- /dev/null +++ b/apps/docs/content/guides/database/extensions/pg_repack.mdx @@ -0,0 +1,99 @@ +--- +id: 'pg_repack' +title: 'pg_repack: Physical storage optimization and maintenance' +description: 'A tool to remove bloat from tables and indexes and optimize physical data order and physical storage' +--- + +[pg_repack](https://github.com/reorg/pg_repack) is a Postgres extension to remove bloat from tables and indexes, and optionally restore the physical order of clustered indexes. Unlike CLUSTER and VACUUM FULL, pg_repack runs "online" and does not hold a exclusive locks on the processed tables that could prevent ongoing database operations. pg_repack's efficiency is comparable to using CLUSTER directly. + +pg_repack provides the following methods to optimize physical storage: + +- Online CLUSTER: ordering table data by cluster index in a non-blocking way +- Ordering table data by specified columns +- Online VACUUM FULL: packing rows only in a non-blocking way +- Rebuild or relocate only the indexes of a table + +pg_repack has 2 components, the database extension and a client-side CLI to control it. + +## Requirements + +- A target table must have a PRIMARY KEY, or a UNIQUE total index on a NOT NULL column. +- Performing a full-table repack requires free disk space about twice as large as the target table and its indexes. + +pg_repack requires the Postgres superuser role by default. That role is not available to users on the Supabase platform. To avoid that requirement, use the `-k` or `--no-superuser-check` flags on every `pg_repack` CLI command. + +The first version of pg_repack with full support for non-superuser repacking is 1.5.2. You can check the version installed on your Supabase instance using + +```sql +select default_version +from pg_available_extensions +where name = 'pg_repack'; +``` + +If pg_repack is not present, or the version is < 1.5.2, [upgrade to the latest version](/docs/guides/platform/upgrading) of Supabase to gain access. + +## Usage + +### Enable the extension + +Get started with pg_repack by enabling the extension in the Supabase Dashboard. + + + + +1. Go to the [Database](https://supabase.com/dashboard/project/_/database/tables) page in the Dashboard. +2. Click on **Extensions** in the sidebar. +3. Search for "pg_repack" and enable the extension. + + + + +{/* prettier-ignore */} +```sql +-- Example: enable the "pg_repack" extension +create extension pg_repack with schema extensions; +``` + + + + +### Install the CLI + +Select an option from the pg_repack docs to [install the client CLI](https://reorg.github.io/pg_repack/#download). + +### Syntax + +All pg_repack commands should include the `-k` flag to skip the client-side superuser check. + +{/* prettier-ignore */} +```sh +pg_repack -k [OPTION]... [DBNAME] +``` + +## Example + +Perform an online `VACUUM FULL` on the tables `public.foo` and `public.bar` in the database `postgres`: + +{/* prettier-ignore */} +```sh +pg_repack -k -h db..supabase.co -p 5432 -U postgres -d postgres --no-order --table public.foo --table public.bar +``` + +See the [official pg_repack documentation](https://reorg.github.io/pg_repack/) for the full list of options. + +## Limitations + +- pg_repack cannot reorganize temporary tables. +- pg_repack cannot cluster tables by GiST indexes. +- You cannot perform DDL commands of the target tables except VACUUM or ANALYZE while pg_repack is working. + pg_repack holds an ACCESS SHARE lock on the target table to enforce this restriction. + +## Resources + +- [Official pg_repack documentation](https://reorg.github.io/pg_repack/) diff --git a/apps/docs/content/guides/database/extensions/pg_stat_monitor.mdx b/apps/docs/content/guides/database/extensions/pg_stat_monitor.mdx deleted file mode 100644 index 843a7332e8e..00000000000 --- a/apps/docs/content/guides/database/extensions/pg_stat_monitor.mdx +++ /dev/null @@ -1,84 +0,0 @@ ---- -id: 'pg_stat_monitor' -title: 'pg_stat_monitor: Query Performance Monitoring' -description: 'Query performance monitoring for PostgreSQL' ---- - -[pg_stat_monitor](https://github.com/percona/pg_stat_monitor) is query performance monitoring tool that provides query execution statistics in a SQL view named `pg_stat_monitor`. It tracks a superset of statistics available in [`pg_stat_statements`](https://www.postgresql.org/docs/current/pgstatstatements.html). Some of the most useful features are: - -- **Time Interval Grouping**: configurable time buckets to track query usage over time -- **Capture Parameters**: optionally track parameters passed into queries instead of generic placeholders e.g. `$1` -- **Query Plan**: store query plans used for execution - -For more information on query optimization, check out the [query performance guide](/docs/guides/platform/performance#examining-query-performance). - -## Enable the extension - - - - -1. Go to the [Database](https://supabase.com/dashboard/project/_/database/tables) page in the Dashboard. -2. Click on **Extensions** in the sidebar. -3. Search for "pg_stat_monitor" and enable the extension. - - - - -{/* prettier-ignore */} -```sql --- Enable the "pg_stat_monitor" extension -create extension pg_stat_monitor with schema extensions; - --- Disable the "pg_stat_monitor" extension -drop extension if exists pg_stat_monitor; -``` - -Even though the SQL code is `create extension`, this is the equivalent of "enabling the extension". -To disable an extension you can call `drop extension`. - -It's good practice to create the extension within a separate schema (like `extensions`) to keep the `public` schema clean. - - - - -## Views - -{/* prettier-ignore */} -```sql -select * from extensions.pg_stat_monitor; -``` - -The following table shows a subset of available columns: - -| Column Type | Description | -| ------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- | -| bucket bigint | Data collection unit. The number shows what bucket in a chain a record belongs to | -| bucket_start_time timestampz | The start time of the bucket | -| userid oid (references pg_authid.oid) | OID of user who executed the statement | -| dbid oid (references pg_database.oid) | OID of database in which the statement was executed | -| toplevel bool | True if the query was executed as a top-level statement (always true if pg_stat_statements.track is set to top) | -| client_ip inet | The IP address of a client that ran the query | -| queryid bigint | Hash code to identify identical normalized queries. | -| planid text | An internally generated ID of a query plan | -| query_plan text | The sequence of steps used to execute a query. This parameter is only available when pgsm_enable_query_plan is enabled | -| query text | Text of a representative statement | -| plans bigint | Number of times the statement was planned (if pg_stat_statements.track_planning is enabled, otherwise zero) | -| total_plan_time double precision | Total time spent planning the statement, in milliseconds (if pg_stat_statements.track_planning is enabled, otherwise zero) | -| min_plan_time double precision | Minimum time spent planning the statement, in milliseconds (if pg_stat_statements.track_planning is enabled, otherwise zero) | - -A full list of statistics is available in the [pg_stat_monitor docs](https://docs.percona.com/pg-stat-monitor/reference.html#postgresql-15). - -## Functions - -- [`pg_stat_monitor_reset()`](https://docs.percona.com/pg-stat-monitor/functions.html): Resets the statistics tracked by the `pg_stat_monitor` view and deletes all previous data. -- [`pg_stat_monitor_version()`](https://docs.percona.com/pg-stat-monitor/functions.html): Displays the version of the `pg_stat_monitor` extension. - -## Resources - -- Official [`pg_stat_monitor` documentation](https://github.com/percona/pg_stat_monitor) diff --git a/apps/docs/content/guides/database/extensions/pg_stat_statements.mdx b/apps/docs/content/guides/database/extensions/pg_stat_statements.mdx index 7c724c3199d..2259a0e62a5 100644 --- a/apps/docs/content/guides/database/extensions/pg_stat_statements.mdx +++ b/apps/docs/content/guides/database/extensions/pg_stat_statements.mdx @@ -6,16 +6,16 @@ description: 'Track planning and execution statistics of all SQL statements exec `pg_stat_statements` is a database extension that exposes a view, of the same name, to track statistics about SQL statements executed on the database. The following table shows some of the available statistics and metadata: -| Column Name | Column Type | Description | -| --------------- | -------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- | -| userid | oid (references pg_authid.oid) | OID of user who executed the statement | -| dbid | oid (references pg_database.oid) | OID of database in which the statement was executed | -| toplevel | bool | True if the query was executed as a top-level statement (always true if pg_stat_statements.track is set to top) | -| queryid | bigint | Hash code to identify identical normalized queries. | -| query | text | Text of a representative statement | -| plans | bigint | Number of times the statement was planned (if pg_stat_statements.track_planning is enabled, otherwise zero) | -| total_plan_time | double precision | Total time spent planning the statement, in milliseconds (if pg_stat_statements.track_planning is enabled, otherwise zero) | -| min_plan_time | double precision | Minimum time spent planning the statement, in milliseconds (if pg_stat_statements.track_planning is enabled, otherwise zero) | +| Column Name | Column Type | Description | +| ----------------- | ------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------- | +| `userid` | `oid` (references `pg_authid.oid`) | OID of user who executed the statement | +| `dbid` | `oid` (references `pg_database.oid`) | OID of database in which the statement was executed | +| `toplevel` | `bool` | True if the query was executed as a top-level statement (always true if pg_stat_statements.track is set to top) | +| `queryid` | `bigint` | Hash code to identify identical normalized queries. | +| `query` | `text` | Text of a representative statement | +| `plans` | `bigint` | Number of times the statement was planned (if pg_stat_statements.track_planning is enabled, otherwise zero) | +| `total_plan_time` | `double precision` | Total time spent planning the statement, in milliseconds (if pg_stat_statements.track_planning is enabled, otherwise zero) | +| `min_plan_time` | `double precision` | Minimum time spent planning the statement, in milliseconds (if pg_stat_statements.track_planning is enabled, otherwise zero) | A full list of statistics is available in the [pg_stat_statements docs](https://www.postgresql.org/docs/current/pgstatstatements.html). diff --git a/apps/docs/content/guides/database/extensions/pgaudit.mdx b/apps/docs/content/guides/database/extensions/pgaudit.mdx index 50d890eabbf..6ea6bf78f9f 100644 --- a/apps/docs/content/guides/database/extensions/pgaudit.mdx +++ b/apps/docs/content/guides/database/extensions/pgaudit.mdx @@ -48,7 +48,7 @@ PGAudit can be configured with different levels of precision. **PGAudit logging precision:** - **[Session](#session-logging):** Logs activity within a connection, such as a [psql](https://supabase.com/docs/guides/database/connecting-to-postgres#connecting-with-psql) connection. -- **[User](#user-logging):** Logs activity by a particular database user (for example, anon or postgres). +- **[User](#user-logging):** Logs activity by a particular database user (for example, `anon` or `postgres`). - **[Global](#global-logging):** Logs activity across the entire database. - **[Object](#object-logging):** Logs events related to specific database objects (for example, the auth.users table). @@ -58,15 +58,15 @@ Although Session, User, and Global modes differ in their precision, they're all These modes can monitor predefined categories of database operations: -| Category | What it Logs | Description | -| -------- | --------------------------------------------------------------------- | -------------------------------------------------------------------------- | -| read | Data retrieval (SELECT, COPY) | Tracks what data is being accessed. | -| write | Data modification (INSERT, DELETE, UPDATE, TRUNCATE, COPY) | Tracks changes made to your database. | -| function | FUNCTION, PROCEDURE, and DO/END block executions | Tracks routine/function executions | -| role | User management actions (CREATE, DROP, ALTER on users and privileges) | Tracks changes to user permissions and access. | -| ddl | Schema changes (CREATE, DROP, ALTER statements) | Monitors modifications to your database structure (tables, indexes, etc.). | -| misc | Less common commands (FETCH, CHECKPOINT) | Captures obscure actions for deeper analysis if needed. | -| all | Everything above | Comprehensive logging for complete audit trails. | +| Category | What it Logs | Description | +| ---------- | --------------------------------------------------------------------- | -------------------------------------------------------------------------- | +| `read` | Data retrieval (SELECT, COPY) | Tracks what data is being accessed. | +| `write` | Data modification (INSERT, DELETE, UPDATE, TRUNCATE, COPY) | Tracks changes made to your database. | +| `function` | FUNCTION, PROCEDURE, and DO/END block executions | Tracks routine/function executions | +| `role` | User management actions (CREATE, DROP, ALTER on users and privileges) | Tracks changes to user permissions and access. | +| `ddl` | Schema changes (CREATE, DROP, ALTER statements) | Monitors modifications to your database structure (tables, indexes, etc.). | +| `misc` | Less common commands (FETCH, CHECKPOINT) | Captures obscure actions for deeper analysis if needed. | +| `all` | Everything above | Comprehensive logging for complete audit trails. | Below is a limited example of how to assign PGAudit to monitor specific categories. @@ -148,13 +148,13 @@ Use global logging cautiously. It can generate many logs and make it difficult t -The below SQL configures PGAudit to record all events associated with the "postgres" role. Since it has extensive privileges, this effectively monitors all database activity. +The below SQL configures PGAudit to record all events associated with the `postgres` role. Since it has extensive privileges, this effectively monitors all database activity. ```sql alter role "postgres" set pgaudit.log to 'all'; ``` -To check if the postgres role is auditing, execute the following command: +To check if the `postgres` role is auditing, execute the following command: ```sql select @@ -279,7 +279,7 @@ API requests are already recorded in the [API Edge Network](https://supabase.com
      -To monitor all writes initiated by the Postgrest API roles: +To monitor all writes initiated by the PostgREST API roles: ```sql alter role "authenticator" set pgaudit.log to 'write'; @@ -383,7 +383,7 @@ PGAudit allows settings to be applied to 3 different database scopes: | Database | Specific database | ALTER DATABASE commands | | Role | Specific user/role | ALTER ROLE commands | -Supabase limits full privileges for file system and database variables, meaning PGAudit modifications can only occur at the role level. Assigning PGAudit to the 'postgres' role grants it nearly complete visibility into the database, making role-level adjustments a practical alternative to configuring at the database or system level. +Supabase limits full privileges for file system and database variables, meaning PGAudit modifications can only occur at the role level. Assigning PGAudit to the `postgres` role grants it nearly complete visibility into the database, making role-level adjustments a practical alternative to configuring at the database or system level. PGAudit's [official documentation](https://www.pgaudit.org) focuses on system and database level configs, but its docs officially supports role level configs, too. diff --git a/apps/docs/content/guides/database/extensions/pgmq.mdx b/apps/docs/content/guides/database/extensions/pgmq.mdx index bd33c51dad7..35155ded592 100644 --- a/apps/docs/content/guides/database/extensions/pgmq.mdx +++ b/apps/docs/content/guides/database/extensions/pgmq.mdx @@ -4,704 +4,4 @@ title: 'pgmq: Queues' description: 'pgmq: Managed queues in Postgres' --- -pgmq is a lightweight message queue built on Postgres. - -## Features - -- Lightweight - No background worker or external dependencies, just Postgres functions packaged in an extension -- "exactly once" delivery of messages to a consumer within a visibility timeout -- API parity with AWS SQS and RSMQ -- Messages stay in the queue until explicitly removed -- Messages can be archived, instead of deleted, for long-term retention and replayability - -## Enable the extension - -```sql -create extension pgmq; -``` - -## Usage [#get-usage] - -### Sending Messages - -#### send - -Send a single message to a queue. - -{/* prettier-ignore */} -```sql -pgmq.send( - queue_name text, - msg jsonb, - delay integer default 0 -) -returns setof bigint -``` - -**Parameters:** - -| Parameter | Type | Description | -| :--------- | :------ | :----------------------------------------------------------------- | -| queue_name | text | The name of the queue | -| msg | jsonb | The message to send to the queue | -| delay | integer | Time in seconds before the message becomes visible. Defaults to 0. | - -Example: - -{/* prettier-ignore */} -```sql -select * from pgmq.send('my_queue', '{"hello": "world"}'); - send ------- - 4 -``` - ---- - -#### send_batch - -Send 1 or more messages to a queue. - -{/* prettier-ignore */} -```sql -pgmq.send_batch( - queue_name text, - msgs jsonb[], - delay integer default 0 -) -returns setof bigint -``` - -**Parameters:** - -| Parameter | Type | Description | -| :--------- | :------ | :------------------------------------------------------------------ | -| queue_name | text | The name of the queue | -| msgs | jsonb[] | Array of messages to send to the queue | -| delay | integer | Time in seconds before the messages becomes visible. Defaults to 0. | - -{/* prettier-ignore */} -```sql -select * from pgmq.send_batch( - 'my_queue', - array[ - '{"hello": "world_0"}'::jsonb, - '{"hello": "world_1"}'::jsonb - ] -); - send_batch ------------- - 1 - 2 -``` - ---- - -### Reading Messages - -#### read - -Read 1 or more messages from a queue. The VT specifies the delay in seconds between reading and the message becoming invisible to other consumers. - -{/* prettier-ignore */} -```sql -pgmq.read( - queue_name text, - vt integer, - qty integer -) - -returns setof pgmq.message_record -``` - -**Parameters:** - -| Parameter | Type | Description | -| :--------- | :------ | :-------------------------------------------------------------- | -| queue_name | text | The name of the queue | -| vt | integer | Time in seconds that the message become invisible after reading | -| qty | integer | The number of messages to read from the queue. Defaults to 1 | - -Example: - -{/* prettier-ignore */} -```sql -select * from pgmq.read('my_queue', 10, 2); - msg_id | read_ct | enqueued_at | vt | message ---------+---------+-------------------------------+-------------------------------+---------------------- - 1 | 1 | 2023-10-28 19:14:47.356595-05 | 2023-10-28 19:17:08.608922-05 | {"hello": "world_0"} - 2 | 1 | 2023-10-28 19:14:47.356595-05 | 2023-10-28 19:17:08.608974-05 | {"hello": "world_1"} -(2 rows) -``` - ---- - -#### read_with_poll - -Same as read(). Also provides convenient long-poll functionality. -When there are no messages in the queue, the function call will wait for `max_poll_seconds` in duration before returning. -If messages reach the queue during that duration, they will be read and returned immediately. - -{/* prettier-ignore */} -```sql - pgmq.read_with_poll( - queue_name text, - vt integer, - qty integer, - max_poll_seconds integer default 5, - poll_interval_ms integer default 100 -) -returns setof pgmq.message_record -``` - -**Parameters:** - -| Parameter | Type | Description | -| :--------------- | :------ | :-------------------------------------------------------------------------- | -| queue_name | text | The name of the queue | -| vt | integer | Time in seconds that the message become invisible after reading. | -| qty | integer | The number of messages to read from the queue. Defaults to 1. | -| max_poll_seconds | integer | Time in seconds to wait for new messages to reach the queue. Defaults to 5. | -| poll_interval_ms | integer | Milliseconds between the internal poll operations. Defaults to 100. | - -Example: - -{/* prettier-ignore */} -```sql -select * from pgmq.read_with_poll('my_queue', 1, 1, 5, 100); - msg_id | read_ct | enqueued_at | vt | message ---------+---------+-------------------------------+-------------------------------+-------------------- - 1 | 1 | 2023-10-28 19:09:09.177756-05 | 2023-10-28 19:27:00.337929-05 | {"hello": "world"} -``` - ---- - -#### pop - -Reads a single message from a queue and deletes it upon read. - -Note: utilization of pop() results in at-most-once delivery semantics if the consuming application does not guarantee processing of the message. - -{/* prettier-ignore */} -```sql -pgmq.pop(queue_name text) -returns setof pgmq.message_record -``` - -**Parameters:** - -| Parameter | Type | Description | -| :--------- | :--- | :-------------------- | -| queue_name | text | The name of the queue | - -Example: - -{/* prettier-ignore */} -```sql -pgmq=# select * from pgmq.pop('my_queue'); - msg_id | read_ct | enqueued_at | vt | message ---------+---------+-------------------------------+-------------------------------+-------------------- - 1 | 2 | 2023-10-28 19:09:09.177756-05 | 2023-10-28 19:27:00.337929-05 | {"hello": "world"} -``` - ---- - -### Deleting/Archiving Messages - -#### delete (single) - -Deletes a single message from a queue. - -{/* prettier-ignore */} -```sql -pgmq.delete (queue_name text, msg_id: bigint) -returns boolean -``` - -**Parameters:** - -| Parameter | Type | Description | -| :--------- | :----- | :---------------------------------- | -| queue_name | text | The name of the queue | -| msg_id | bigint | Message ID of the message to delete | - -Example: - -{/* prettier-ignore */} -```sql -select pgmq.delete('my_queue', 5); - delete --------- - t -``` - ---- - -#### delete (batch) - -Delete one or many messages from a queue. - -{/* prettier-ignore */} -```sql -pgmq.delete (queue_name text, msg_ids: bigint[]) -returns setof bigint -``` - -**Parameters:** - -| Parameter | Type | Description | -| :--------- | :------- | :----------------------------- | -| queue_name | text | The name of the queue | -| msg_ids | bigint[] | Array of message IDs to delete | - -Examples: - -Delete two messages that exist. - -{/* prettier-ignore */} -```sql -select * from pgmq.delete('my_queue', array[2, 3]); - delete --------- - 2 - 3 -``` - -Delete two messages, one that exists and one that does not. Message `999` does not exist. - -```sql -select * from pgmq.delete('my_queue', array[6, 999]); - delete --------- - 6 -``` - ---- - -#### purge_queue - -Permanently deletes all messages in a queue. Returns the number of messages that were deleted. - -```text -purge_queue(queue_name text) -returns bigint -``` - -**Parameters:** - -| Parameter | Type | Description | -| :--------- | :--- | :-------------------- | -| queue_name | text | The name of the queue | - -Example: - -Purge the queue when it contains 8 messages; - -{/* prettier-ignore */} -```sql -select * from pgmq.purge_queue('my_queue'); - purge_queue -------------- - 8 -``` - ---- - -#### archive (single) - -Removes a single requested message from the specified queue and inserts it into the queue's archive. - -{/* prettier-ignore */} -```sql -pgmq.archive(queue_name text, msg_id bigint) -returns boolean -``` - -**Parameters:** - -| Parameter | Type | Description | -| :--------- | :----- | :----------------------------------- | -| queue_name | text | The name of the queue | -| msg_id | bigint | Message ID of the message to archive | - -Returns -Boolean value indicating success or failure of the operation. - -Example; remove message with ID 1 from queue `my_queue` and archive it: - -{/* prettier-ignore */} -```sql -select * from pgmq.archive('my_queue', 1); - archive ---------- - t -``` - ---- - -#### archive (batch) - -Deletes a batch of requested messages from the specified queue and inserts them into the queue's archive. -Returns an array of message ids that were successfully archived. - -```text -pgmq.archive(queue_name text, msg_ids bigint[]) -RETURNS SETOF bigint -``` - -**Parameters:** - -| Parameter | Type | Description | -| :--------- | :------- | :------------------------------ | -| queue_name | text | The name of the queue | -| msg_ids | bigint[] | Array of message IDs to archive | - -Examples: - -Delete messages with ID 1 and 2 from queue `my_queue` and move to the archive. - -{/* prettier-ignore */} -```sql -select * from pgmq.archive('my_queue', array[1, 2]); - archive ---------- - 1 - 2 -``` - -Delete messages 4, which exists and 999, which does not exist. - -{/* prettier-ignore */} -```sql -select * from pgmq.archive('my_queue', array[4, 999]); - archive ---------- - 4 -``` - ---- - -### Queue Management - -#### create - -Create a new queue. - -{/* prettier-ignore */} -```sql -pgmq.create(queue_name text) -returns void -``` - -**Parameters:** - -| Parameter | Type | Description | -| :--------- | :--- | :-------------------- | -| queue_name | text | The name of the queue | - -Example: - -{/* prettier-ignore */} -```sql -select from pgmq.create('my_queue'); - create --------- -``` - ---- - -#### create_partitioned - -Create a partitioned queue. - -{/* prettier-ignore */} -```sql -pgmq.create_partitioned ( - queue-ue_name text, - partition_interval text default '10000'::text, - retention_interval text default '100000'::text -) -returns void -``` - -**Parameters:** - -| Parameter | Type | Description | -| :----------------- | :--- | :-------------------- | -| queue_name | text | The name of the queue | -| partition_interval | text | The name of the queue | -| retention_interval | text | The name of the queue | - -Example: - -Create a queue with 100,000 messages per partition, and will retain 10,000,000 messages on old partitions. Partitions greater than this will be deleted. - -{/* prettier-ignore */} -```sql -select * from pgmq.create_partitioned( - 'my_partitioned_queue', - '100000', - '10000000' -); - create_partitioned --------------------- -``` - ---- - -#### create_unlogged - -Creates an unlogged table. This is useful when write throughput is more important that durability. -See Postgres documentation for [unlogged tables](https://www.postgresql.org/docs/current/sql-createtable.html#SQL-CREATETABLE-UNLOGGED) for more information. - -{/* prettier-ignore */} -```sql -pgmq.create_unlogged(queue_name text) -returns void -``` - -**Parameters:** - -| Parameter | Type | Description | -| :--------- | :--- | :-------------------- | -| queue_name | text | The name of the queue | - -Example: - -{/* prettier-ignore */} -```sql -select pgmq.create_unlogged('my_unlogged'); - create_unlogged ------------------ -``` - ---- - -#### detach_archive - -Drop the queue's archive table as a member of the PGMQ extension. Useful for preventing the queue's archive table from being drop when `drop extension pgmq` is executed. -This does not prevent the further archives() from appending to the archive table. - -{/* prettier-ignore */} -```sql -pgmq.detach_archive(queue_name text) -``` - -**Parameters:** - -| Parameter | Type | Description | -| :--------- | :--- | :-------------------- | -| queue_name | text | The name of the queue | - -Example: - -{/* prettier-ignore */} -```sql -select * from pgmq.detach_archive('my_queue'); - detach_archive ----------------- -``` - ---- - -#### drop_queue - -Deletes a queue and its archive table. - -{/* prettier-ignore */} -```sql -pgmq.drop_queue(queue_name text) -returns boolean -``` - -**Parameters:** - -| Parameter | Type | Description | -| :--------- | :--- | :-------------------- | -| queue_name | text | The name of the queue | - -Example: - -{/* prettier-ignore */} -```sql -select * from pgmq.drop_queue('my_unlogged'); - drop_queue ------------- - t -``` - -### Utilities - -#### set_vt - -Sets the visibility timeout of a message to a specified time duration in the future. Returns the record of the message that was updated. - -{/* prettier-ignore */} -```sql -pgmq.set_vt( - queue_name text, - msg_id bigint, - vt_offset integer -) -returns pgmq.message_record -``` - -**Parameters:** - -| Parameter | Type | Description | -| :--------- | :------ | :-------------------------------------------------------------------- | -| queue_name | text | The name of the queue | -| msg_id | bigint | ID of the message to set visibility time | -| vt_offset | integer | Duration from now, in seconds, that the message's VT should be set to | - -Example: - -Set the visibility timeout of message 1 to 30 seconds from now. - -```sql -select * from pgmq.set_vt('my_queue', 11, 30); - msg_id | read_ct | enqueued_at | vt | message ---------+---------+-------------------------------+-------------------------------+---------------------- - 1 | 0 | 2023-10-28 19:42:21.778741-05 | 2023-10-28 19:59:34.286462-05 | {"hello": "world_0"} -``` - ---- - -#### list_queues - -List all the queues that currently exist. - -{/* prettier-ignore */} -```sql -list_queues() -RETURNS TABLE( - queue_name text, - created_at timestamp with time zone, - is_partitioned boolean, - is_unlogged boolean -) -``` - -Example: - -{/* prettier-ignore */} -```sql -select * from pgmq.list_queues(); - queue_name | created_at | is_partitioned | is_unlogged -----------------------+-------------------------------+----------------+------------- - my_queue | 2023-10-28 14:13:17.092576-05 | f | f - my_partitioned_queue | 2023-10-28 19:47:37.098692-05 | t | f - my_unlogged | 2023-10-28 20:02:30.976109-05 | f | t -``` - ---- - -#### metrics - -Get metrics for a specific queue. - -{/* prettier-ignore */} -```sql -pgmq.metrics(queue_name: text) -returns table( - queue_name text, - queue_length bigint, - newest_msg_age_sec integer, - oldest_msg_age_sec integer, - total_messages bigint, - scrape_time timestamp with time zone -) -``` - -**Parameters:** - -| Parameter | Type | Description | -| :--------- | :--- | :-------------------- | -| queue_name | text | The name of the queue | - -**Returns:** - -| Attribute | Type | Description | -| :----------------- | :----------------------- | :------------------------------------------------------------------------ | -| queue_name | text | The name of the queue | -| queue_length | bigint | Number of messages currently in the queue | -| newest_msg_age_sec | integer \| null | Age of the newest message in the queue, in seconds | -| oldest_msg_age_sec | integer \| null | Age of the oldest message in the queue, in seconds | -| total_messages | bigint | Total number of messages that have passed through the queue over all time | -| scrape_time | timestamp with time zone | The current timestamp | - -Example: - -{/* prettier-ignore */} -```sql -select * from pgmq.metrics('my_queue'); - queue_name | queue_length | newest_msg_age_sec | oldest_msg_age_sec | total_messages | scrape_time -------------+--------------+--------------------+--------------------+----------------+------------------------------- - my_queue | 16 | 2445 | 2447 | 35 | 2023-10-28 20:23:08.406259-05 -``` - ---- - -#### metrics_all - -Get metrics for all existing queues. - -```text -pgmq.metrics_all() -RETURNS TABLE( - queue_name text, - queue_length bigint, - newest_msg_age_sec integer, - oldest_msg_age_sec integer, - total_messages bigint, - scrape_time timestamp with time zone -) -``` - -**Returns:** - -| Attribute | Type | Description | -| :----------------- | :----------------------- | :------------------------------------------------------------------------ | -| queue_name | text | The name of the queue | -| queue_length | bigint | Number of messages currently in the queue | -| newest_msg_age_sec | integer \| null | Age of the newest message in the queue, in seconds | -| oldest_msg_age_sec | integer \| null | Age of the oldest message in the queue, in seconds | -| total_messages | bigint | Total number of messages that have passed through the queue over all time | -| scrape_time | timestamp with time zone | The current timestamp | - -{/* prettier-ignore */} -```sql -select * from pgmq.metrics_all(); - queue_name | queue_length | newest_msg_age_sec | oldest_msg_age_sec | total_messages | scrape_time -----------------------+--------------+--------------------+--------------------+----------------+------------------------------- - my_queue | 16 | 2563 | 2565 | 35 | 2023-10-28 20:25:07.016413-05 - my_partitioned_queue | 1 | 11 | 11 | 1 | 2023-10-28 20:25:07.016413-05 - my_unlogged | 1 | 3 | 3 | 1 | 2023-10-28 20:25:07.016413-05 -``` - -### Types - -#### message_record - -The complete representation of a message in a queue. - -| Attribute Name | Type | Description | -| :------------- | :----------------------- | :--------------------------------------------------------------------- | -| msg_id | bigint | Unique ID of the message | -| read_ct | bigint | Number of times the message has been read. Increments on read(). | -| enqueued_at | timestamp with time zone | time that the message was inserted into the queue | -| vt | timestamp with time zone | Timestamp when the message will become available for consumers to read | -| message | jsonb | The message payload | - -Example: - -{/* prettier-ignore */} -```sql - msg_id | read_ct | enqueued_at | vt | message ---------+---------+-------------------------------+-------------------------------+-------------------- - 1 | 1 | 2023-10-28 19:06:19.941509-05 | 2023-10-28 19:06:27.419392-05 | {"hello": "world"} -``` - -## Resources - -- Official Docs: [pgmq/api](https://tembo.io/pgmq/#creating-a-queue) +See the [Supabase Queues docs](/docs/guides/queues). diff --git a/apps/docs/content/guides/database/extensions/pgsodium.mdx b/apps/docs/content/guides/database/extensions/pgsodium.mdx index dbddf13b51f..5c3a9d4c92b 100644 --- a/apps/docs/content/guides/database/extensions/pgsodium.mdx +++ b/apps/docs/content/guides/database/extensions/pgsodium.mdx @@ -8,6 +8,12 @@ Supabase DOES NOT RECOMMEND any new usage of [`pgsodium`](https://github.com/mic The [`pgsodium`](https://github.com/michelp/pgsodium) extension is expected to go through a deprecation cycle in the near future. We will reach out to owners of impacted projects to assist with migrations away from [`pgsodium`](https://github.com/michelp/pgsodium) once the deprecation process begins. + + +The [Vault extension](/docs/guides/database/vault) won’t be impacted. Its internal implementation will shift away from pgsodium, but the interface and API will remain unchanged. + + + [`pgsodium`](https://github.com/michelp/pgsodium) is a Postgres extension which provides SQL access to [`libsodium`'s](https://doc.libsodium.org/) high-level cryptographic algorithms. Supabase previously documented two features derived from pgsodium. Namely [Server Key Management](https://github.com/michelp/pgsodium#server-key-management) and [Transparent Column Encryption](https://github.com/michelp/pgsodium#transparent-column-encryption). At this time, we do not recommend using either on the Supabase platform due to their high level of operational complexity and misconfiguration risk. diff --git a/apps/docs/content/guides/database/extensions/pgvector.mdx b/apps/docs/content/guides/database/extensions/pgvector.mdx index b320a7e23a5..c505abe6991 100644 --- a/apps/docs/content/guides/database/extensions/pgvector.mdx +++ b/apps/docs/content/guides/database/extensions/pgvector.mdx @@ -102,6 +102,20 @@ const { data, error } = await supabase.from('posts').insert({ }) ``` +## Specific usage cases + +### Queries with filtering + +If you use an IVFFlat or HNSW index and naively filter the results based on the value of another column, you may get fewer rows returned than requested. + +For example, the following query may return fewer than 5 rows, even if 5 corresponding rows exist in the database. This is because the embedding index may not return 5 rows matching the filter. + +``` +SELECT * FROM items WHERE category_id = 123 ORDER BY embedding <-> '[3,1,2]' LIMIT 5; +``` + +To get the exact number of requested rows, use [iterative search](https://github.com/pgvector/pgvector/?tab=readme-ov-file#iterative-index-scans) to continue scanning the index until enough results are found. + ## More pgvector and Supabase resources - [Supabase Clippy: ChatGPT for Supabase Docs](https://supabase.com/blog/chatgpt-supabase-docs) diff --git a/apps/docs/content/guides/database/extensions/postgis.mdx b/apps/docs/content/guides/database/extensions/postgis.mdx index 9c470366db1..40da5901fe3 100644 --- a/apps/docs/content/guides/database/extensions/postgis.mdx +++ b/apps/docs/content/guides/database/extensions/postgis.mdx @@ -11,7 +11,7 @@ tocVideo: 'agFsGDJxjwA' While you may be able to store simple lat/long geographic coordinates as a set of decimals, it does not scale very well when you try to query through a large data set. PostGIS comes with special data types that are efficient, and indexable for high scalability. -The additional data types that PostGIS provides include [Point](https://postgis.net/docs/using_postgis_dbmanagement.html#Point), [Polygon](https://postgis.net/docs/using_postgis_dbmanagement.html#Polygon), [Linestring](https://postgis.net/docs/using_postgis_dbmanagement.html#LineString), and many more to represent different types of geographical data. In this guide, we will mainly focus on how to interact with `Point` type, which represents a single set of latitude and longitude. If you are interested in digging deeper, you can learn more about different data types on the [data management section of PostGIS docs](https://postgis.net/docs/using_postgis_dbmanagement.html). +The additional data types that PostGIS provides include [Point](https://postgis.net/docs/using_postgis_dbmanagement.html#Point), [Polygon](https://postgis.net/docs/using_postgis_dbmanagement.html#Polygon), [LineString](https://postgis.net/docs/using_postgis_dbmanagement.html#LineString), and many more to represent different types of geographical data. In this guide, we will mainly focus on how to interact with `Point` type, which represents a single set of latitude and longitude. If you are interested in digging deeper, you can learn more about different data types on the [data management section of PostGIS docs](https://postgis.net/docs/using_postgis_dbmanagement.html). ## Enable the extension @@ -28,7 +28,7 @@ You can get started with PostGIS by enabling the PostGIS extension in your Supab 1. Go to the [Database](https://supabase.com/dashboard/project/_/database/tables) page in the Dashboard. 2. Click on **Extensions** in the sidebar. -3. Search for "postgis" and enable the extension. +3. Search for `postgis` and enable the extension. 4. In the confirmation prompt select "Create a new schema" and name it `gis` for example. @@ -58,7 +58,7 @@ Let’s create our table. Each row represents a restaurant with its location sto create table if not exists public.restaurants ( id int generated by default as identity primary key, name text not null, - location geography(POINT) not null + location gis.geography(POINT) not null ); ``` @@ -85,12 +85,16 @@ You can insert geographical data through SQL or through our API.

      Restaurants

      +{/* supa-mdx-lint-disable Rule003Spelling */} + | id | name | location | | --- | ----------- | -------------------------------- | | 1 | Supa Burger | lat: 40.807416, long: -73.946823 | | 2 | Supa Pizza | lat: 40.807475, long: -73.94581 | | 3 | Supa Taco | lat: 40.80629, long: -73.945826 | +{/* supa-mdx-lint-enable Rule003Spelling */} + @@ -98,9 +102,9 @@ You can insert geographical data through SQL or through our API. insert into public.restaurants (name, location) values - ('Supa Burger', st_point(-73.946823, 40.807416)), - ('Supa Pizza', st_point(-73.94581, 40.807475)), - ('Supa Taco', st_point(-73.945826, 40.80629)); + ('Supa Burger', gis.st_point(-73.946823, 40.807416)), + ('Supa Pizza', gis.st_point(-73.94581, 40.807475)), + ('Supa Taco', gis.st_point(-73.945826, 40.80629)); ``` @@ -203,15 +207,22 @@ Sorting datasets from closest to farthest, sometimes called nearest-neighbor sor ```sql create or replace function nearby_restaurants(lat float, long float) returns table (id public.restaurants.id%TYPE, name public.restaurants.name%TYPE, lat float, long float, dist_meters float) +set search_path = '' language sql as $$ - select id, name, st_y(location::geometry) as lat, st_x(location::geometry) as long, st_distance(location, st_point(long, lat)::geography) as dist_meters + select id, name, gis.st_y(location::gis.geometry) as lat, gis.st_x(location::gis.geometry) as long, gis.st_distance(location, gis.st_point(long, lat)::gis.geography) as dist_meters from public.restaurants - order by location <-> st_point(long, lat)::geography; + order by location operator(gis.<->) gis.st_point(long, lat)::gis.geography; $$; ``` -You can call this function from your client using `rpc()` like this: +Before being able to call this function from our client we need to grant access to our `gis` schema: + +```sql +grant usage on schema gis to anon, authenticated; +``` + +Now you can call this function from your client using `rpc()` like this: -As of PostGIS 2.3 or newer, the PostGIS extension is no longer relocatable from one schema to another. If you need to move it from one schema to another for any reason (Eg. from the public schema to the extensions schema for security reasons), you would normally run a ALTER EXTENSION to relocate the schema. However, you will now to do the following steps: +As of PostGIS 2.3 or newer, the PostGIS extension is no longer relocatable from one schema to another. If you need to move it from one schema to another for any reason (e.g. from the public schema to the extensions schema for security reasons), you would normally run a ALTER EXTENSION to relocate the schema. However, you will now to do the following steps: 1. Backup your Database to prevent data loss - You can do this through the [CLI](https://supabase.com/docs/reference/cli/supabase-db-dump) or Postgres backup tools such as [pg_dumpall](https://www.postgresql.org/docs/current/backup-dump.html#BACKUP-DUMP-ALL) diff --git a/apps/docs/content/guides/database/extensions/postgres_fdw.mdx b/apps/docs/content/guides/database/extensions/postgres_fdw.mdx new file mode 100644 index 00000000000..9b248cb0331 --- /dev/null +++ b/apps/docs/content/guides/database/extensions/postgres_fdw.mdx @@ -0,0 +1,148 @@ +--- +id: 'postgres_fdw' +title: 'postgres_fdw' +description: 'Query Postgres server from another' +--- + +The extension enables Postgres to query tables and views on a remote Postgres server. + +## Enable the extension + + + + + 1. Go to the [Database](/dashboard/project/_/database/tables) page in the Dashboard. + 2. Click on **Extensions** in the sidebar. + 3. Search for "postgres_fdw" and enable the extension. + + + + + ```sql + -- Example: enable the "postgres_fdw" extension + create extension if not exists postgres_fdw; + + -- Example: disable the "postgres_fdw" extension + drop extension if exists postgres_fdw; + ``` + + Procedural languages are automatically installed within `pg_catalog`, so you don't need to specify a schema. + + + + + +## Create a connection to another database + + + + + + Define the remote database address + + + + ```sql + create server "" + foreign data wrapper postgres_fdw + options ( + host '', + port '', + dbname '' + ); + ``` + + + + + + Set the user credentials for the remote server + + + ```sql + create user mapping for "" + server "" + options ( + user '', + password '' + ); + ``` + + + + + + Import tables from the foreign database + + + Example: Import all tables from a schema + ```sql + import foreign schema "" + from server "" + into ""; + ``` + + Example: Import specific tables + ```sql + import foreign schema "" + limit to ( + "", + "" + ) + from server "" + into ""; + ``` + + + + + + + ```sql + select * from "" + ``` + + + + + +### Configuring execution options + +#### Fetch_size + +Maximum rows fetched per operation. For example, fetching 200 rows with `fetch_size` set to 100 requires 2 requests. + +```sql +alter server "" +options (fetch_size '10000'); +``` + +#### Batch_size + +Maximum rows inserted per cycle. For example, inserting 200 rows with `batch_size` set to 100 requires 2 requests. + +```sql +alter server "" +options (batch_size '1000'); +``` + +#### Extensions + +Lists shared extensions. Without them, queries involving unlisted extension functions or operators may fail or omit references. + +```sql +alter server "" +options (extensions 'vector, postgis'); +``` + +For more server options, check the extension's [official documentation](https://www.postgresql.org/docs/current/postgres-fdw.html#POSTGRES-FDW) + +## Resources + +- Official [`postgres_fdw` documentation](https://www.postgresql.org/docs/current/postgres-fdw.html#POSTGRES-FDW) diff --git a/apps/docs/content/guides/database/extensions/rum.mdx b/apps/docs/content/guides/database/extensions/rum.mdx index e6fbe850a61..4185efebd3b 100644 --- a/apps/docs/content/guides/database/extensions/rum.mdx +++ b/apps/docs/content/guides/database/extensions/rum.mdx @@ -4,7 +4,7 @@ title: 'RUM: improved inverted index for full-text search based on GIN index' description: 'A GIN-like index with additional tree-organized data for each index entry' --- -[RUM](https://github.com/postgrespro/rum) is an extension which adds a RUM index to Postgresql. +[RUM](https://github.com/postgrespro/rum) is an extension which adds a RUM index to Postgres. RUM index is based on GIN that stores additional per-entry information in a posting tree. For example, positional information of lexemes or timestamps. In comparison to GIN it can use this information to make faster index-only scans for: @@ -18,10 +18,10 @@ combinations or phrases. Main operators for ordering are: -tsvector `<=>` tsquery | float4 | Distance between tsvector and tsquery. -value `<=>` value | float8 | Distance between two values. +`tsvector` `<=>` `tsquery` | `float4` | Distance between `tsvector` and `tsquery`. +value `<=>` value | `float8` | Distance between two values. -Where value is timestamp, timestamptz, int2, int4, int8, float4, float8, money and oid +Where value is `timestamp`, `timestamptz`, `int2`, `int4`, `int8`, `float4`, `float8`, `money` and `oid` ## Usage @@ -58,7 +58,7 @@ drop extension if exists rum; ### Syntax -#### For type: tsvector +#### For type: `tsvector` To understand the following you may need first to see [Official PostgreSQL documentation on text search](https://www.postgresql.org/docs/current/functions-textsearch.html) @@ -116,11 +116,11 @@ SELECT id, d, d `<=>` '2016-05-16 14:21:25' FROM tsts WHERE t @@ 'wr&qh' ORDER B (5 rows) ``` -#### For type: anyarray +#### For type: `anyarray` `rum_anyarray_ops` -This operator class stores anyarray elements with length of the array. It supports operators `&&`, `@>`, `<@`, `=`, `%` operators. It also supports ordering by `<=>` operator. +This operator class stores `anyarray` elements with length of the array. It supports operators `&&`, `@>`, `<@`, `=`, `%` operators. It also supports ordering by `<=>` operator. ```sql CREATE TABLE test_array (i int2[]); @@ -143,7 +143,7 @@ SELECT * FROM test_array WHERE i && '{1}' ORDER BY i `<=>` '{1}' ASC; `rum_anyarray_addon_ops` -The does the same with anyarray index as `rum_tsvector_addon_ops` i.e. allows to order select results using distance +The does the same with `anyarray` index as `rum_tsvector_addon_ops` i.e. allows to order select results using distance operator by attached column. ## Limitations diff --git a/apps/docs/content/guides/database/extensions/timescaledb.mdx b/apps/docs/content/guides/database/extensions/timescaledb.mdx index a97dd8260af..53277248488 100644 --- a/apps/docs/content/guides/database/extensions/timescaledb.mdx +++ b/apps/docs/content/guides/database/extensions/timescaledb.mdx @@ -4,7 +4,7 @@ title: 'timescaledb: Time-Series data' description: 'Scalable time-series data storage and analysis' --- -[timescaledb](https://docs.timescale.com/timescaledb/latest/) is a PostgreSQL extension designed for improved handling of time-series data. It provides a scalable, high-performance solution for storing and querying time-series data on top of a standard PostgreSQL database. +[`timescaledb`](https://docs.timescale.com/timescaledb/latest/) is a PostgreSQL extension designed for improved handling of time-series data. It provides a scalable, high-performance solution for storing and querying time-series data on top of a standard PostgreSQL database. `timescaledb` uses a time-series-aware storage model and indexing techniques to improve performance of PostgreSQL in working with time-series data. The extension divides data into chunks based on time intervals, allowing it to scale efficiently, especially for large data sets. The data is then compressed, optimized for write-heavy workloads, and partitioned for parallel processing. `timescaledb` also includes a set of functions, operators, and indexes that work with time-series data to reduce query times, and make data easier to work with. @@ -27,7 +27,7 @@ Supabase projects come with [TimescaleDB Apache 2 Edition](https://docs.timescal 1. Go to the [Database](https://supabase.com/dashboard/project/_/database/tables) page in the Dashboard. 2. Click on **Extensions** in the sidebar. -3. Search for "timescaledb" and enable the extension. +3. Search for `timescaledb` and enable the extension. diff --git a/apps/docs/content/guides/database/extensions/uuid-ossp.mdx b/apps/docs/content/guides/database/extensions/uuid-ossp.mdx index 4fd6e9e3495..48e8ce2945e 100644 --- a/apps/docs/content/guides/database/extensions/uuid-ossp.mdx +++ b/apps/docs/content/guides/database/extensions/uuid-ossp.mdx @@ -27,7 +27,7 @@ Currently `uuid-ossp` extension is enabled by default and cannot be disabled. 1. Go to the [Database](https://supabase.com/dashboard/project/_/database/tables) page in the Dashboard. 2. Click on **Extensions** in the sidebar. -3. Search for "uuid-ossp" and enable the extension. +3. Search for `uuid-ossp` and enable the extension. diff --git a/apps/docs/content/guides/database/full-text-search.mdx b/apps/docs/content/guides/database/full-text-search.mdx index 0975aa2fe04..004d8fe9b1f 100644 --- a/apps/docs/content/guides/database/full-text-search.mdx +++ b/apps/docs/content/guides/database/full-text-search.mdx @@ -30,7 +30,7 @@ For this guide we'll use the following example data: > -{/* */} +{/* supa-mdx-lint-disable Rule003Spelling */} | id | title | author | description | | --- | ----------------------------------- | ---------------------- | ------------------------------------------------------------------ | @@ -40,7 +40,7 @@ For this guide we'll use the following example data: | 4 | Green Eggs and Ham | Dr. Seuss | Sam has changing food preferences and eats unusually colored food. | | 5 | Harry Potter and the Goblet of Fire | J.K. Rowling | Fourth year of school starts, big drama ensues. | -{/* */} +{/* supa-mdx-lint-enable Rule003Spelling */} @@ -301,14 +301,14 @@ data = supabase.from_('books').select().text_search('description', "'big'").exec -{/* */} +{/* supa-mdx-lint-disable Rule003Spelling */} | id | title | author | description | | --- | ----------------------------------- | ----------------- | ----------------------------------------------- | | 3 | Tootle | Gertrude Crampton | Little toy train has big dreams. | | 5 | Harry Potter and the Goblet of Fire | J.K. Rowling | Fourth year of school starts, big drama ensues. | -{/* */} +{/* supa-mdx-lint-enable Rule003Spelling */} @@ -420,14 +420,14 @@ data = supabase.from_('books').select().text_search('title_description', "little -{/* */} +{/* supa-mdx-lint-disable Rule003Spelling */} | id | title | author | description | | --- | --------------------- | ---------------------- | ------------------------------------------- | | 1 | The Poky Little Puppy | Janette Sebring Lowrey | Puppy is slower than other, bigger animals. | | 3 | Tootle | Gertrude Crampton | Little toy train has big dreams. | -{/* */} +{/* supa-mdx-lint-enable Rule003Spelling */} @@ -507,13 +507,13 @@ data = supabase.from_('books').select().text_search('description', "'little' & ' -{/* */} +{/* supa-mdx-lint-disable Rule003Spelling */} | id | title | author | description | | --- | ------ | ----------------- | -------------------------------- | | 3 | Tootle | Gertrude Crampton | Little toy train has big dreams. | -{/* */} +{/* supa-mdx-lint-enable Rule003Spelling */} @@ -593,14 +593,14 @@ response = client.from_('books').select().text_search('description', "'little' | -{/* */} +{/* supa-mdx-lint-disable Rule003Spelling */} | id | title | author | description | | --- | --------------------- | ---------------------- | ------------------------------------------- | | 1 | The Poky Little Puppy | Janette Sebring Lowrey | Puppy is slower than other, bigger animals. | | 3 | Tootle | Gertrude Crampton | Little toy train has big dreams. | -{/* */} +{/* supa-mdx-lint-enable Rule003Spelling */} @@ -699,7 +699,7 @@ select * from search_books_by_title_prefix('Little+Puppy'); ## Creating indexes -Now that we have Full Text Search working, let's create an `index`. This will allow Postgres to "build" the documents pre-emptively so that they +Now that we have Full Text Search working, let's create an `index`. This will allow Postgres to "build" the documents preemptively so that they don't need to be created at the time we execute the query. This will make our queries much faster. ### Searchable columns @@ -734,8 +734,7 @@ from books; -{/* */} - +``` | id | fts | | --- | --------------------------------------------------------------------------------------------------------------- | | 1 | 'anim':7 'bigger':6 'littl':10 'poki':9 'puppi':1,11 'slower':3 | @@ -743,8 +742,7 @@ from books; | 3 | 'big':5 'dream':6 'littl':1 'tootl':7 'toy':2 'train':3 | | 4 | 'chang':3 'color':9 'eat':7 'egg':12 'food':4,10 'green':11 'ham':14 'prefer':5 'sam':1 'unus':8 | | 5 | 'big':6 'drama':7 'ensu':8 'fire':15 'fourth':1 'goblet':13 'harri':9 'potter':10 'school':4 'start':5 'year':2 | - -{/* */} +``` @@ -820,13 +818,13 @@ data = client.from_('books').select().text_search('fts', "'little' & 'big'").exe -{/* */} +{/* supa-mdx-lint-disable Rule003Spelling */} | id | title | author | description | fts | | --- | ------ | ----------------- | -------------------------------- | ------------------------------------------------------- | | 3 | Tootle | Gertrude Crampton | Little toy train has big dreams. | 'big':5 'dream':6 'littl':1 'tootl':7 'toy':2 'train':3 | -{/* */} +{/* supa-mdx-lint-enable Rule003Spelling */} diff --git a/apps/docs/content/guides/database/functions.mdx b/apps/docs/content/guides/database/functions.mdx index 8bf1bddc162..58883b17e81 100644 --- a/apps/docs/content/guides/database/functions.mdx +++ b/apps/docs/content/guides/database/functions.mdx @@ -82,7 +82,7 @@ select hello_world(); const { data, error } = await supabase.rpc('hello_world') ``` -Reference: [rpc()](../../reference/javascript/rpc) +Reference: [`rpc()`](../../reference/javascript/rpc) @@ -92,7 +92,7 @@ final data = await supabase .rpc('hello_world'); ``` -Reference: [rpc()](../../reference/dart/rpc) +Reference: [`rpc()`](../../reference/dart/rpc) @@ -101,7 +101,7 @@ Reference: [rpc()](../../reference/dart/rpc) try await supabase.rpc("hello_world").execute() ``` -Reference: [rpc()](../../reference/swift/rpc) +Reference: [`rpc()`](../../reference/swift/rpc) @@ -110,7 +110,7 @@ Reference: [rpc()](../../reference/swift/rpc) val data = supabase.postgrest.rpc("hello_world") ``` -Reference: [rpc()](../../reference/kotlin/rpc) +Reference: [`rpc()`](../../reference/kotlin/rpc) @@ -119,7 +119,7 @@ Reference: [rpc()](../../reference/kotlin/rpc) data = supabase.rpc('hello_world').execute() ``` -Reference: [rpc()](../../reference/python/rpc) +Reference: [`rpc()`](../../reference/python/rpc) @@ -141,20 +141,24 @@ For example, if we had a database with some Star Wars data inside:

      Planets

      +``` | id | name | | --- | -------- | -| 1 | Tattoine | +| 1 | Tatooine | | 2 | Alderaan | | 3 | Kashyyyk | +```

      People

      +``` | id | name | planet_id | | --- | ---------------- | --------- | | 1 | Anakin Skywalker | 1 | | 2 | Luke Skywalker | 1 | | 3 | Princess Leia | 2 | | 4 | Chewbacca | 3 | +``` diff --git a/apps/docs/content/guides/database/inspect.mdx b/apps/docs/content/guides/database/inspect.mdx index 4552a1697e5..44d95c0bf20 100644 --- a/apps/docs/content/guides/database/inspect.mdx +++ b/apps/docs/content/guides/database/inspect.mdx @@ -1,9 +1,22 @@ --- -id: 'json' title: 'Debugging and monitoring' description: 'Inspecting your Postgres database for common issues around disk, query performance, index, locks, and more using the terminal.' --- +Database performance is a large topic and many factors can contribute. Some of the most common causes of poor performance include: + +- An inefficiently designed schema +- Inefficiently designed queries +- A lack of indexes causing slower than required queries over large tables +- Unused indexes causing slow `INSERT`, `UPDATE` and `DELETE` operations +- Not enough compute resources, such as memory, causing your database to go to disk for results too often +- Lock contention from multiple queries operating on highly utilized tables +- Large amount of bloat on your tables causing poor query planning + +You can examine your database and queries for these issues using either the [Supabase CLI](/docs/guides/local-development/cli/getting-started) or SQL. + +## Using the CLI + The Supabase CLI comes with a range of tools to help inspect your Postgres instances for potential issues. The CLI gets the information from Postgres internals. Therefore, most tools provided are compatible with any Postgres databases regardless if they are a Supabase project or not. You can find installation instructions for the the Supabase CLI here. @@ -47,9 +60,9 @@ supabase link --project-ref Then the CLI will automatically connect to your Supabase project whenever you are in the project folder and you no longer need to provide `—db-url`. -## Inspection commands +### Inspection commands -Below are the db inspection commands provided, grouped by different use cases. +Below are the `db` inspection commands provided, grouped by different use cases. @@ -57,7 +70,7 @@ Some commands might require `pg_stat_statements` to be enabled or a specific Pos -### Disk storage +#### Disk storage These commands are handy if you are running low on disk storage: @@ -68,7 +81,7 @@ These commands are handy if you are running low on disk storage: - [index-sizes](/docs/reference/cli/supabase-inspect-db-index-sizes) - shows the sizes of individual index - [table-index-sizes](/docs/reference/cli/supabase-inspect-db-table-index-sizes) - shows the sizes of indexes for each table -### Query performance +#### Query performance The commands below are useful if your Postgres database consumes a lot of resources like CPU, RAM or Disk IO. You can also use them to investigate slow queries. @@ -79,17 +92,17 @@ The commands below are useful if your Postgres database consumes a lot of resour - [long-running-queries](/docs/reference/cli/supabase-inspect-db-long-running-queries) - shows long running queries that are executing right now - [outliers](/docs/reference/cli/supabase-inspect-db-outliers) - shows queries with high execution time but low call count and queries with high proportion of execution time spent on synchronous I/O -### Locks +#### Locks - [locks](/docs/reference/cli/supabase-inspect-db-locks) - shows statements which have taken out an exclusive lock on a relation - [blocking](/docs/reference/cli/supabase-inspect-db-blocking) - shows statements that are waiting for locks to be released -### Connections +#### Connections - [role-connections](/docs/reference/cli/supabase-inspect-db-role-connections) - shows number of active connections for all database roles (Supabase-specific command) - [replication-slots](/docs/reference/cli/supabase-inspect-db-replication-slots) - shows information about replication slots on the database -## Notes on `pg_stat_statements` +### Notes on `pg_stat_statements` Following commands require `pg_stat_statements` to be enabled: calls, locks, cache-hit, blocking, unused-indexes, index-usage, bloat, outliers, table-record-counts, replication-slots, seq-scans, vacuum-stats, long-running-queries. @@ -97,6 +110,155 @@ When using `pg_stat_statements` also take note that it only stores the latest 5, Learn more about pg_stats [here](https://supabase.com/docs/guides/database/extensions/pg_stat_statements). -## Acknowledgements +## Using SQL -Supabase CLI's inspect commands are heavily inspired by the pg-extras tools. + + +If you're seeing an `insufficient privilege` error when viewing the Query Performance page from the dashboard, run this command: + +```shell +$ grant pg_read_all_stats to postgres; +``` + + + +### Postgres cumulative statistics system + +Postgres collects data about its own operations using the [cumulative statistics system](https://www.postgresql.org/docs/current/monitoring-stats.html). In addition to this, every Supabase project has the [pg_stat_statements extension](/docs/guides/database/extensions/pg_stat_statements) enabled by default. This extension records query execution performance details and is the best way to find inefficient queries. This information can be combined with the Postgres query plan analyzer to develop more efficient queries. + +Here are some example queries to get you started. + +### Most frequently called queries + +```sql +select + auth.rolname, + statements.query, + statements.calls, + -- -- Postgres 13, 14, 15 + statements.total_exec_time + statements.total_plan_time as total_time, + statements.min_exec_time + statements.min_plan_time as min_time, + statements.max_exec_time + statements.max_plan_time as max_time, + statements.mean_exec_time + statements.mean_plan_time as mean_time, + -- -- Postgres <= 12 + -- total_time, + -- min_time, + -- max_time, + -- mean_time, + statements.rows / statements.calls as avg_rows +from + pg_stat_statements as statements + inner join pg_authid as auth on statements.userid = auth.oid +order by statements.calls desc +limit 100; +``` + +This query shows: + +- query statistics, ordered by the number of times each query has been executed +- the role that ran the query +- the number of times it has been called +- the average number of rows returned +- the cumulative total time the query has spent running +- the min, max and mean query times. + +This provides useful information about the queries you run most frequently. Queries that have high `max_time` or `mean_time` times and are being called often can be good candidates for optimization. + +### Slowest queries by execution time + +```sql +select + auth.rolname, + statements.query, + statements.calls, + -- -- Postgres 13, 14, 15 + statements.total_exec_time + statements.total_plan_time as total_time, + statements.min_exec_time + statements.min_plan_time as min_time, + statements.max_exec_time + statements.max_plan_time as max_time, + statements.mean_exec_time + statements.mean_plan_time as mean_time, + -- -- Postgres <= 12 + -- total_time, + -- min_time, + -- max_time, + -- mean_time, + statements.rows / statements.calls as avg_rows +from + pg_stat_statements as statements + inner join pg_authid as auth on statements.userid = auth.oid +order by max_time desc +limit 100; +``` + +This query will show you statistics about queries ordered by the maximum execution time. It is similar to the query above ordered by calls, but this one highlights outliers that may have high executions times. Queries which have high or mean execution times are good candidates for optimization. + +### Most time consuming queries + +```sql +select + auth.rolname, + statements.query, + statements.calls, + statements.total_exec_time + statements.total_plan_time as total_time, + to_char( + ( + (statements.total_exec_time + statements.total_plan_time) / sum( + statements.total_exec_time + statements.total_plan_time + ) over () + ) * 100, + 'FM90D0' + ) || '%' as prop_total_time +from + pg_stat_statements as statements + inner join pg_authid as auth on statements.userid = auth.oid +order by total_time desc +limit 100; +``` + +This query will show you statistics about queries ordered by the cumulative total execution time. It shows the total time the query has spent running as well as the proportion of total execution time the query has taken up. + +Queries which are the most time consuming are not necessarily bad, you may have a very efficient and frequently ran queries that end up taking a large total % time, but it can be useful to help spot queries that are taking up more time than they should. + +### Hit rate + +Generally for most applications a small percentage of data is accessed more regularly than the rest. To make sure that your regularly accessed data is available, Postgres tracks your data access patterns and keeps this in its [shared_buffers](https://www.postgresql.org/docs/15/runtime-config-resource.html#RUNTIME-CONFIG-RESOURCE-MEMORY) cache. + +Applications with lower cache hit rates generally perform more poorly since they have to hit the disk to get results rather than serving them from memory. Very poor hit rates can also cause you to burst past your [Disk IO limits](./compute-add-ons#disk-io) causing significant performance issues. + +You can view your cache and index hit rate by executing the following query: + +```sql +select + 'index hit rate' as name, + (sum(idx_blks_hit)) / nullif(sum(idx_blks_hit + idx_blks_read), 0) * 100 as ratio +from pg_statio_user_indexes +union all +select + 'table hit rate' as name, + sum(heap_blks_hit) / nullif(sum(heap_blks_hit) + sum(heap_blks_read), 0) * 100 as ratio +from pg_statio_user_tables; +``` + +This shows the ratio of data blocks fetched from the Postgres [shared_buffers](https://www.postgresql.org/docs/15/runtime-config-resource.html#RUNTIME-CONFIG-RESOURCE-MEMORY) cache against the data blocks that were read from disk/OS cache. + +If either of your index or table hit rate are < 99% then this can indicate your compute plan is too small for your current workload and you would benefit from more memory. [Upgrading your compute](./compute-add-ons) is easy and can be done from your [project dashboard](https://supabase.com/dashboard/project/_/settings/compute-and-disk). + +### Optimizing poor performing queries + +Postgres has built in tooling to help you optimize poorly performing queries. You can use the [query plan analyzer](https://www.postgresql.org/docs/current/sql-explain.html) on any expensive queries that you have identified: + +```sql +explain analyze ; +``` + +When you include `analyze` in the explain statement, the database attempts to execute the query and provides a detailed query plan along with actual execution times. So, be careful using `explain analyze` with `insert`/`update`/`delete` queries, because the query will actually run, and could have unintended side-effects. + +If you run just `explain` without the `analyze` keyword, the database will only perform query planning without actually executing the query. This approach can be beneficial when you want to inspect the query plan without affecting the database or if you encounter timeouts in your queries. + +Using the query plan analyzer to optimize your queries is a large topic, with a number of online resources available: + +- [Official docs.](https://www.postgresql.org/docs/current/using-explain.html) +- [The Art of PostgreSQL.](https://theartofpostgresql.com/explain-plan-visualizer/) +- [Postgres Wiki.](https://wiki.postgresql.org/wiki/Using_EXPLAIN) +- [Enterprise DB.](https://www.enterprisedb.com/blog/postgresql-query-optimization-performance-tuning-with-explain-analyze) + +You can pair the information available from `pg_stat_statements` with the detailed system metrics available [via your metrics endpoint](../platform/metrics) to better understand the behavior of your DB and the queries you're executing against it. diff --git a/apps/docs/content/guides/database/joins-and-nesting.mdx b/apps/docs/content/guides/database/joins-and-nesting.mdx index 5285543e530..8921f0e568d 100644 --- a/apps/docs/content/guides/database/joins-and-nesting.mdx +++ b/apps/docs/content/guides/database/joins-and-nesting.mdx @@ -8,7 +8,7 @@ The data APIs automatically detect relationships between Postgres tables. Since ## One-to-many joins -Let's use an example database that stores `countries` and `cities`: +Let's use an example database that stores `orchestral_sections` and `instruments`: -**Countries** +**Orchestral sections** -| `id` | `name` | -| ---- | -------------- | -| 1 | United Kingdom | -| 2 | United States | +| `id` | `name` | +| ---- | --------- | +| 1 | strings | +| 2 | woodwinds | -**Cities** +**Instruments** -| `id` | `name` | `country_id` | -| ---- | ----------- | ------------ | -| 1 | London | 1 | -| 2 | Manchester | 1 | -| 3 | Los Angeles | 2 | -| 4 | New York | 2 | +| `id` | `name` | `section_id` | +| ---- | ------ | ------------ | +| 1 | violin | 1 | +| 2 | viola | 1 | +| 3 | flute | 2 | +| 4 | oboe | 2 | ```sql -create table countries ( +create table orchestral_sections ( "id" serial primary key, "name" text ); -insert into countries +insert into orchestral_sections (id, name) values - (1, 'United Kingdom'), - (2, 'United States'); + (1, 'strings'), + (2, 'woodwinds'); -create table cities ( +create table instruments ( "id" serial primary key, "name" text, - "country_id" int references "countries" + "section_id" int references "orchestral_sections" ); -insert into cities - (name, country_id) +insert into instruments + (name, section_id) values - ('London', 1), - ('Manchester', 1), - ('Los Angeles', 2), - ('New York', 2); + ('violin', 1), + ('viola', 1), + ('flute', 2), + ('oboe', 2); ``` @@ -80,10 +80,10 @@ The APIs will automatically detect relationships based on the foreign keys: ```js -const { data, error } = await supabase.from('countries').select(` - id, - name, - cities ( id, name ) +const { data, error } = await supabase.from('orchestral_sections').select(` + id, + name, + instruments ( id, name ) `) ``` @@ -93,66 +93,66 @@ const { data, error } = await supabase.from('countries').select(` These helper types provide the result types from any query, including nested types for database joins. -Given the following schema with a relation between cities and countries: +Given the following schema with a relation between orchestral sections and instruments: ```sql -create table countries ( +create table orchestral_sections ( "id" serial primary key, "name" text ); -create table cities ( +create table instruments ( "id" serial primary key, "name" text, - "country_id" int references "countries" + "section_id" int references "orchestral_sections" ); ``` -We can get the nested `CountriesWithCities` type like this: +We can get the nested `SectionsWithInstruments` type like this: ```ts import { QueryResult, QueryData, QueryError } from '@supabase/supabase-js' -const countriesWithCitiesQuery = supabase.from('countries').select(` +const sectionsWithInstrumentsQuery = supabase.from('orchestral_sections').select(` id, name, - cities ( + instruments ( id, name ) `) -type CountriesWithCities = QueryData +type SectionsWithInstruments = QueryData -const { data, error } = await countriesWithCitiesQuery +const { data, error } = await sectionsWithInstrumentsQuery if (error) throw error -const countriesWithCities: CountriesWithCities = data +const sectionsWithInstruments: SectionsWithInstruments = data ``` ```dart -final data = await supabase.from('countries').select('id, name, cities(id, name)'); +final data = await supabase.from('orchestral_sections').select('id, name, instruments(id, name)'); ``` ```swift -struct Country: Codable { +struct OrchestralSection: Codable { let id: Int let name: String - let cities: [City] + let instruments: [Instrument] - struct City: Codable { + struct Instrument: Codable { let id: Int let name: String } } -let countries: [Country] = try await supabase - .from("countries") - .select("id, name, cities(id, name)") +let orchestralSections: [OrchestralSection] = try await supabase + .from("orchestral_sections") + .select("id, name, instruments(id, name)") .execute() .value ``` @@ -161,14 +161,14 @@ let countries: [Country] = try await supabase ```kotlin -val data = supabase.from("countries").select(Columns.raw("id, name, cities(id, name)")) +val data = supabase.from("orchestral_sections").select(Columns.raw("id, name, instruments(id, name)")) ``` ```python -data = supabase.from_('countries').select('id, name, cities(id, name)').execute() +data = supabase.from_('orchestral_sections').select('id, name, instruments(id, name)').execute() ``` @@ -177,13 +177,13 @@ data = supabase.from_('countries').select('id, name, cities(id, name)').execute( ```javascript const Query = ` query { - countriesCollection { + orchestral_sectionsCollection { edges { node { id - name - cities { - id, + name + instruments { + id, name } } @@ -197,7 +197,7 @@ const Query = ` ```bash -GET https://[REF].supabase.co/rest/v1/countries?select=id,name,cities(id,name) +GET https://[REF].supabase.co/rest/v1/orchestral_sections?select=id,name,instruments(id,name) ``` @@ -238,8 +238,8 @@ In these cases you don't need to explicitly define the joining table (members). ```js const { data, error } = await supabase.from('teams').select(` - id, - team_name, + id, + team_name, users ( id, name ) `) ``` @@ -387,7 +387,7 @@ const { data, error } = await supabase.from('shifts').select( badge_scan_time ), end_scan:scans!scan_id_end ( - id, + id, user_id, badge_scan_time ) diff --git a/apps/docs/content/guides/database/json.mdx b/apps/docs/content/guides/database/json.mdx index a1195bfe912..5a067043f46 100644 --- a/apps/docs/content/guides/database/json.mdx +++ b/apps/docs/content/guides/database/json.mdx @@ -117,6 +117,8 @@ values 2. Select the `books` table in the sidebar. 3. Click **+ Insert row** and add 5 rows with the following properties: +{/* supa-mdx-lint-disable Rule003Spelling */} + | id | title | author | metadata | | --- | ----------------------------------- | ---------------------- | --------------------------------------------------------------------------------------------------------------------- | | 1 | The Poky Little Puppy | Janette Sebring Lowrey | `json {"ages":[3,6],"price":5.95,"description":"Puppy is slower than other, bigger animals."}` | @@ -125,6 +127,8 @@ values | 4 | Green Eggs and Ham | Dr. Seuss | `json {"ages":[4,8],"price":7.49,"description":"Sam has changing food preferences and eats unusually colored food."}` | | 5 | Harry Potter and the Goblet of Fire | J.K. Rowling | `json {"ages":[10,99],"price":24.95,"description":"Fourth year of school starts, big drama ensues."}` | +{/* supa-mdx-lint-enable Rule003Spelling */} + diff --git a/apps/docs/content/guides/database/metabase.mdx b/apps/docs/content/guides/database/metabase.mdx new file mode 100644 index 00000000000..be3b298a1ba --- /dev/null +++ b/apps/docs/content/guides/database/metabase.mdx @@ -0,0 +1,72 @@ +--- +id: 'metabase' +title: 'Connecting to Metabase' +breadcrumb: 'GUI Quickstarts' +hideToc: true +--- + +[`Metabase`](https://www.metabase.com/) is an Open Source data visualization tool. You can use it to explore your data stored in Supabase. + + + + + + + + Create a [Metabase account](https://store.metabase.com/checkout) or deploy locally with [Docker](https://www.docker.com/products/docker-desktop/) + + + + + + Deploying with Docker: + ```sh + docker pull metabase/metabase:latest + ``` + + Then run: + ```sh + docker run -d -p 3000:3000 --name metabase metabase/metabase + ``` + + The server should be available at [`http://localhost:3000/setup`](http://localhost:3000/setup) + + + + + + + + + + Connect your Postgres server to Metabase. + - Visit the [Database Settings](https://supabase.com/dashboard/project/_/settings/database) + - Find your Session Mode string. It should end with 5432 + - Enter your database credentials into Metabase + + + + + Example credentials: + ![Name Postgres Server.](/docs/img/guides/database/connecting-to-postgres/metabase/add-pg-server.png) + + + + + + + + + That's it! Explore your data in Metabase + + + + + + ![explore data](/docs/img/guides/database/connecting-to-postgres/metabase/explore.png) + + + + + + diff --git a/apps/docs/content/guides/database/orioledb.mdx b/apps/docs/content/guides/database/orioledb.mdx new file mode 100644 index 00000000000..a83faabf945 --- /dev/null +++ b/apps/docs/content/guides/database/orioledb.mdx @@ -0,0 +1,137 @@ +--- +id: 'orioledb' +title: 'OrioleDB Overview' +description: "A storage extension for PostgreSQL which uses PostgreSQL's pluggable storage system" +--- + +The [OrioleDB](https://www.orioledb.com/) Postgres extension provides a drop-in replacement storage engine for the default heap storage method. It is designed to improve Postgres' scalability and performance. + +OrioleDB addresses PostgreSQL's scalability limitations by removing bottlenecks in the shared memory cache under high concurrency. It also optimizes write-ahead-log (WAL) insertion through row-level WAL logging. These changes lead to significant improvements in the industry standard TPC-C benchmark, which approximates a real-world transactional workload. The following benchmark was performed on a c7g.metal instance and shows OrioleDB's performance outperforming the default PostgreSQL heap method with a 3.3x speedup. + +TPC-C (warehouses = 500) + + + +OrioleDB is in active development and currently has [certain limitations](https://www.orioledb.com/docs/usage/getting-started#current-limitations). Currently, only B-tree indexes are supported, so features like pg_vector's HNSW indexes are not yet available. An Index Access Method bridge to unlock support for all index types used with heap storage is under active development. In the Supabase OrioleDB image the default storage method has been updated to use OrioleDB, granting better performance out of the box. + + + +## Concepts + +### Index-organized tables + +OrioleDB uses index-organized tables, where table data is stored in the index structure. This design eliminates the need for separate heap storage, reduces overhead and improves lookup performance for primary key queries. + +### No buffer mapping + +In-memory pages are connected to the storage pages using direct links. This allows OrioleDB to bypass PostgreSQL's shared buffer pool and eliminate the associated complexity and contention in buffer mapping. + +### Undo log + +Multi-Version Concurrency Control (MVCC) is implemented using an undo log. The undo log stores previous row versions and transaction information, which enables consistent reads while removing the need for table vacuuming completely. + +### Copy-on-write checkpoints + +OrioleDB implements copy-on-write checkpoints to persist data efficiently. This approach writes only modified data during a checkpoint, reducing the I/O overhead compared to traditional PostgreSQL checkpointing and allowing row-level WAL logging. + +## Usage + +### Creating OrioleDB project + +You can get started with OrioleDB by enabling the extension in your Supabase dashboard. +To get started with OrioleDB you need to [create a new Supabase project](https://supabase.com/dashboard/new/_) and choose `OrioleDB Public Alpha` Postgres version. + +Creating OrioleDB project + +### Creating tables + +To create a table using the OrioleDB storage engine just execute the standard `CREATE TABLE` statement. By default it will create a table using OrioleDB storage engine. For example: + +```sql +-- Create a table +create table blog_post ( + id int8 not null, + title text not null, + body text not null, + author text not null, + published_at timestamptz not null default CURRENT_TIMESTAMP, + views bigint not null, + primary key (id) +); +``` + +### Creating indexes + +OrioleDB tables always have a primary key. If it wasn't defined explicitly, a hidden primary key is created using the `ctid` column. +Additionally you can create secondary indexes. + + + +Currently, only B-tree indexes are supported, so features like pg_vector's HNSW indexes are not yet available. + + + +```sql +-- Create an index +create index blog_post_published_at on blog_post (published_at); + +create index blog_post_views on blog_post (views) where (views > 1000); +``` + +### Data manipulation + +You can query and modify data in OrioleDB tables using standard SQL statements, including `SELECT`, `INSERT`, `UPDATE`, `DELETE` and `INSERT ... ON CONFLICT`. + +```sql +INSERT INTO blog_post (id, title, body, author, views) +VALUES (1, 'Hello, World!', 'This is my first blog post.', 'John Doe', 1000); + +SELECT * FROM blog_post ORDER BY published_at DESC LIMIT 10; + id │ title │ body │ author │ published_at │ views +────┼───────────────┼─────────────────────────────┼──────────┼───────────────────────────────┼─────── + 1 │ Hello, World! │ This is my first blog post. │ John Doe │ 2024-11-15 12:04:18.756824+01 │ 1000 +``` + +### Viewing query plans + +You can see the execution plan using standard `EXPLAIN` statement. + +```sql +EXPLAIN SELECT * FROM blog_post ORDER BY published_at DESC LIMIT 10; + QUERY PLAN +──────────────────────────────────────────────────────────────────────────────────────────────────────────── + Limit (cost=0.15..1.67 rows=10 width=120) + -> Index Scan Backward using blog_post_published_at on blog_post (cost=0.15..48.95 rows=320 width=120) + +EXPLAIN SELECT * FROM blog_post WHERE id = 1; + QUERY PLAN +────────────────────────────────────────────────────────────────────────────────── + Index Scan using blog_post_pkey on blog_post (cost=0.15..8.17 rows=1 width=120) + Index Cond: (id = 1) + +EXPLAIN (ANALYZE, BUFFERS) SELECT * FROM blog_post ORDER BY published_at DESC LIMIT 10; + QUERY PLAN +────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── + Limit (cost=0.15..1.67 rows=10 width=120) (actual time=0.052..0.054 rows=1 loops=1) + -> Index Scan Backward using blog_post_published_at on blog_post (cost=0.15..48.95 rows=320 width=120) (actual time=0.050..0.052 rows=1 loops=1) + Planning Time: 0.186 ms + Execution Time: 0.088 ms +``` + +## Resources + +- [Official OrioleDB documentation](https://www.orioledb.com/docs) +- [OrioleDB GitHub repository](https://github.com/orioledb/orioledb) diff --git a/apps/docs/content/guides/database/pgadmin.mdx b/apps/docs/content/guides/database/pgadmin.mdx index 99297520d89..7608ad02112 100644 --- a/apps/docs/content/guides/database/pgadmin.mdx +++ b/apps/docs/content/guides/database/pgadmin.mdx @@ -70,7 +70,7 @@ hideToc: true Download your SSL certificate from Dashboard's [`Database Settings`](https://supabase.com/dashboard/project/_/settings/database). - In pgAdmin, navigate to the Parameters tab and select connection parameter as Root Certificate. Next navigate to the Root certificate input, it will open up a file-picker modal. Select the certificate you downloaded earlier and save the server details. PgAdmin should now be able to connect to your Postgres via SSL. + In pgAdmin, navigate to the Parameters tab and select connection parameter as Root Certificate. Next navigate to the Root certificate input, it will open up a file-picker modal. Select the certificate you downloaded earlier and save the server details. pgAdmin should now be able to connect to your Postgres via SSL. diff --git a/apps/docs/content/guides/database/postgres/column-level-security.mdx b/apps/docs/content/guides/database/postgres/column-level-security.mdx index 6ed691e326c..b5452a9d7a2 100644 --- a/apps/docs/content/guides/database/postgres/column-level-security.mdx +++ b/apps/docs/content/guides/database/postgres/column-level-security.mdx @@ -12,7 +12,7 @@ PostgreSQL's [Row Level Security (RLS)](https://www.postgresql.org/docs/current/ roles.
      -## Policies at the Row Level +## Policies at the row level Policies in Row Level Security (RLS) are used to restrict access to rows in a table. Think of them like adding a `WHERE` clause to every query. @@ -35,7 +35,7 @@ update However, this gives the post owner full access to update the row, including all of the columns. -## Privileges at the Column Level +## Privileges at the column level To restrict access to columns, you can use [Privileges](https://www.postgresql.org/docs/current/ddl-priv.html). @@ -80,9 +80,9 @@ You can view and edit the privileges in the [Supabase Studio](https://supabase.c ![Column level privileges](/docs/img/guides/privileges/column-level-privileges-2.png) -## Manage column privileges in Migrations +## Manage column privileges in migrations -While you can manage privileges directly from the Dashboard, as your project grows you may want to manage them in your migrations. Read about database migrations in the [Local Development](https://supabase.com/docs/guides/getting-started/local-development#database-migrations) guide. +While you can manage privileges directly from the Dashboard, as your project grows you may want to manage them in your migrations. Read about database migrations in the [Local Development](https://supabase.com/docs/guides/deployment/database-migrations) guide. diff --git a/apps/docs/content/guides/database/postgres/custom-claims-and-role-based-access-control-rbac.mdx b/apps/docs/content/guides/database/postgres/custom-claims-and-role-based-access-control-rbac.mdx index 74a208de77e..e5ff8ea539b 100644 --- a/apps/docs/content/guides/database/postgres/custom-claims-and-role-based-access-control-rbac.mdx +++ b/apps/docs/content/guides/database/postgres/custom-claims-and-role-based-access-control-rbac.mdx @@ -246,8 +246,8 @@ You can read more about using functions in RLS policies in the [RLS guide](/docs You can then use the `authorize` method within your RLS policies. For example, to enable the desired delete access, you would add the following policies: ```sql -create policy "Allow authorized delete access" on public.channels for delete using ( (SELECT authorize('channels.delete')) ); -create policy "Allow authorized delete access" on public.messages for delete using ( (SELECT authorize('messages.delete')) ); +create policy "Allow authorized delete access" on public.channels for delete to authenticated using ( (SELECT authorize('channels.delete')) ); +create policy "Allow authorized delete access" on public.messages for delete to authenticated using ( (SELECT authorize('messages.delete')) ); ``` ## Accessing custom claims in your application @@ -267,7 +267,7 @@ const { subscription: authListener } = supabase.auth.onAuthStateChange(async (ev }) ``` -For server-side logic you can use packages like [express-jwt](https://github.com/auth0/express-jwt), [koa-jwt](https://github.com/stiang/koa-jwt), [PyJWT](https://github.com/jpadilla/pyjwt), [dart_jsonwebtoken](https://pub.dev/packages/dart_jsonwebtoken), [Microsoft.AspNetCore.Authentication.JwtBearer](https://www.nuget.org/packages/Microsoft.AspNetCore.Authentication.JwtBearer), etc. +For server-side logic you can use packages like [`express-jwt`](https://github.com/auth0/express-jwt), [`koa-jwt`](https://github.com/stiang/koa-jwt), [`PyJWT`](https://github.com/jpadilla/pyjwt), [dart_jsonwebtoken](https://pub.dev/packages/dart_jsonwebtoken), [Microsoft.AspNetCore.Authentication.JwtBearer](https://www.nuget.org/packages/Microsoft.AspNetCore.Authentication.JwtBearer), etc. ## Conclusion diff --git a/apps/docs/content/guides/database/postgres/indexes.mdx b/apps/docs/content/guides/database/postgres/indexes.mdx index 9796f952733..acbbff3daf4 100644 --- a/apps/docs/content/guides/database/postgres/indexes.mdx +++ b/apps/docs/content/guides/database/postgres/indexes.mdx @@ -26,7 +26,7 @@ create table persons ( ); ``` - + All the queries in this guide can be run using the [SQL Editor](https://supabase.com/dashboard/project/_/sql) in the Supabase Dashboard, or via `psql` if you're [connecting directly to the database](/docs/guides/database/connecting-to-postgres#direct-connections). @@ -121,7 +121,7 @@ For more information on the Index Advisor and its suggestions, see the [`index_a To use the Dashboard Index Advisor: -1. Go to the [Query Performance](/dashboard/project/_/database/query-performance) page. +1. Go to the [Query Performance](/dashboard/project/_/advisors/query-performance) page. 1. Click on a query to bring up the Details side panel. 1. Select the Indexes tab. 1. Enable Index Advisor if prompted. diff --git a/apps/docs/content/guides/database/postgres/roles.mdx b/apps/docs/content/guides/database/postgres/roles.mdx index b72ab8c1ad2..009df3a5684 100644 --- a/apps/docs/content/guides/database/postgres/roles.mdx +++ b/apps/docs/content/guides/database/postgres/roles.mdx @@ -38,15 +38,15 @@ Your Postgres database is the core of your Supabase project, so it's important t ### Special symbols in passwords -If you use special symbols in your postgres password, you must remember to [percent-encode](https://en.wikipedia.org/wiki/Percent-encoding) your password later if using the postgres connection string, for example, `postgresql://postgres.projectref:p%3Dword@aws-0-us-east-1.pooler.supabase.com:6543/postgres` +If you use special symbols in your Postgres password, you must remember to [percent-encode](https://en.wikipedia.org/wiki/Percent-encoding) your password later if using the Postgres connection string, for example, `postgresql://postgres.projectref:p%3Dword@aws-0-us-east-1.pooler.supabase.com:6543/postgres` ### Changing your project password When you created your project you were also asked to enter a password. This is actually the password for the `postgres` role in your database. You can update this from the Dashboard under the [database settings](https://supabase.com/dashboard/project/_/settings/database) page. You should _never_ give this to third-party service unless you absolutely trust them. Instead, we recommend that you create a new user for every service that you want to give access too. This will also help you with debugging - you can see every query that each role is executing in your database within `pg_stat_statements`. -Changing the password does not result in any downtime. All connected services, such as postgrest, pgbouncer, and other Supabase managed services, are automatically updated to use the latest password to ensure availability. However, if you have any external services connecting to the Supabase database using hardcoded username/password credentials, a manual update will be required. +Changing the password does not result in any downtime. All connected services, such as PostgREST, PgBouncer, and other Supabase managed services, are automatically updated to use the latest password to ensure availability. However, if you have any external services connecting to the Supabase database using hardcoded username/password credentials, a manual update will be required. -## Granting Permissions +## Granting permissions Roles can be granted various permissions on database objects using the `GRANT` command. Permissions include `SELECT`, `INSERT`, `UPDATE`, and `DELETE`. You can configure access to almost any object inside your database - including tables, views, functions, and triggers. @@ -72,7 +72,7 @@ create role "child_role_name" inherit "parent_role_name"; ### Preventing inheritance -In some cases, you might want to prevent a role from having a child relationship (typically superuser roles). You can prevent inheritance relations using NOINHERIT: +In some cases, you might want to prevent a role from having a child relationship (typically superuser roles). You can prevent inheritance relations using `NOINHERIT`: ```sql alter role "child_role_name" noinherit; @@ -117,7 +117,7 @@ For running commands via the Supabase UI. ### `supabase_admin` -Supabase Administrative role for maintaining your database. +An internal role Supabase uses for administrative tasks, such as running upgrades and automations. ## Resources diff --git a/apps/docs/content/guides/database/postgres/row-level-security.mdx b/apps/docs/content/guides/database/postgres/row-level-security.mdx index 26ced6b8455..d2090a1ffb7 100644 --- a/apps/docs/content/guides/database/postgres/row-level-security.mdx +++ b/apps/docs/content/guides/database/postgres/row-level-security.mdx @@ -9,16 +9,25 @@ When you need granular authorization rules, nothing beats Postgres's [Row Level ## Row Level Security in Supabase + + +Supabase allows convenient and secure data access from the browser, as long as you enable RLS. + +RLS _must_ always be enabled on any tables stored in an exposed schema. By default, this is the `public` schema. + +RLS is enabled by default on tables created with the Table Editor in the dashboard. If you create one in raw SQL or with the SQL editor, remember to enable RLS yourself: + +```sql +alter table . +enable row level security; +``` + + + RLS is incredibly powerful and flexible, allowing you to write complex SQL rules that fit your unique business needs. RLS can be [combined with Supabase Auth](/docs/guides/database/postgres/row-level-security) for end-to-end user security from the browser to the database. RLS is a Postgres primitive and can provide "[defense in depth]()" to protect your data from malicious actors even when accessed through third-party tooling. - - -You should _always_ enable RLS on tables created in a public schema. This is done for you when you create a table with the Table Editor. If you create one in raw SQL or with the SQL Editor, remember to enable RLS yourself. - - - ## Policies [Policies](https://www.postgresql.org/docs/current/sql-createpolicy.html) are Postgres's rule engine. Policies are easy to understand once you get the hang of them. Each policy is attached to a table, and the policy is executed every time a table is accessed. diff --git a/apps/docs/content/guides/database/postgres/setup-replication-external.mdx b/apps/docs/content/guides/database/postgres/setup-replication-external.mdx index 5418c47051a..8621755ff4f 100644 --- a/apps/docs/content/guides/database/postgres/setup-replication-external.mdx +++ b/apps/docs/content/guides/database/postgres/setup-replication-external.mdx @@ -25,7 +25,7 @@ select pg_create_logical_replication_slot('example_slot', 'pgoutput'); 3. Now we will connect to our **external database** and subscribe to our `publication` Note: ): - + This will need a direct connection to your database and you can find the connection info in the [Dashboard](https://supabase.com/dashboard/project/_/settings/database). @@ -42,7 +42,7 @@ PUBLICATION example_pub WITH (copy_data = true, create_slot=false, slot_name=example_slot); ``` - + `create_slot` is set to `false` because `slot_name` is provided and the slot was already created in Step 2. To copy data from before the slot was created, set `copy_data` to `true`. diff --git a/apps/docs/content/guides/database/postgres/timeouts.mdx b/apps/docs/content/guides/database/postgres/timeouts.mdx index 1b41032cbd4..88d7fdc359a 100644 --- a/apps/docs/content/guides/database/postgres/timeouts.mdx +++ b/apps/docs/content/guides/database/postgres/timeouts.mdx @@ -3,9 +3,11 @@ title: Timeouts subtitle: Extend database timeouts to execute longer transactions --- -Requests made through the Supabase API and Dashboard have a timeout of 60 seconds. The database also has a global default timeout of 2 minutes. + -To execute longer transactions, connect to your database using [Supavisor](/docs/guides/database/connecting-to-postgres#connection-pooler) or the [direct connection string](/docs/guides/database/connecting-to-postgres#direct-connections), and change the timeout settings. +Dashboard and [Client](/docs/guides/api/rest/client-libs) queries have a max-configurable timeout of 60 seconds. For longer transactions, use [Supavisor or direct connections](/docs/guides/database/connecting-to-postgres#quick-summary). + + ## Change Postgres timeout @@ -59,7 +61,7 @@ The default role timeouts are: - `anon`: 3s - `authenticated`: 8s -- `service_role`: none (capped by API to be 60s) +- `service_role`: none (defaults to the `authenticator` role's 8s timeout if unset) - `postgres`: none (capped by default global timeout to be 2min) Run the following query to change a role's timeout: @@ -69,8 +71,7 @@ alter role example_role set statement_timeout = '10min'; -- could also use secon ``` - If you are changing the timeout for the Supabase Client API calls, you will need to reload - postgREST to reflect the timeout changes by running the following script: + If you are changing the timeout for the Supabase Client API calls, you will need to reload PostgREST to reflect the timeout changes by running the following script: ```sql NOTIFY pgrst, 'reload config'; @@ -124,9 +125,9 @@ language sql; The Supabase Dashboard contains tools to help you identify timed-out and long-running queries. -### Using the Log Explorer +### Using the Logs Explorer -Go to the [Log Explorer](/dashboard/project/_/logs/explorer), and run the following query to identify timed-out events (`statement timeout`) and queries that successfully run for longer than 10 seconds (`duration`). +Go to the [Logs Explorer](/dashboard/project/_/logs/explorer), and run the following query to identify timed-out events (`statement timeout`) and queries that successfully run for longer than 10 seconds (`duration`). ```sql select @@ -153,22 +154,22 @@ limit 100; ### Using the Query Performance page -Go to the [Query Performance page](/dashboard/project/_/database/query-performance?preset=slowest_execution) and filter by relevant role and query speeds. This only identifies slow-running but successful queries. Unlike the Log Explorer, it does not show you timed-out queries. +Go to the [Query Performance page](/dashboard/project/_/advisors/query-performance?preset=slowest_execution) and filter by relevant role and query speeds. This only identifies slow-running but successful queries. Unlike the Log Explorer, it does not show you timed-out queries. ### Understanding roles in logs Each API server uses a designated user for connecting to the database: -| Role | API/Tool | -| -------------------------- | ------------------------------------------------------------------------- | -| supabase_admin | Used by Supabase to configure projects and for monitoring | -| authenticator | PostgREST | -| supabase_auth_admin | Auth | -| supabase_storage_admin | Storage | -| supabase_realtime_admin | Realtime | -| supabase_replication_admin | Synchronizes Read Replicas | -| postgres | Supabase Dashboard and External Tools (e.g., Prisma, SQLAlchemy, PSQL...) | -| Custom roles | External Tools (e.g., Prisma, SQLAlchemy, PSQL...) | +| Role | API/Tool | +| ---------------------------- | ------------------------------------------------------------------------- | +| `supabase_admin` | Used by Supabase to configure projects and for monitoring | +| `authenticator` | PostgREST | +| `supabase_auth_admin` | Auth | +| `supabase_storage_admin` | Storage | +| `supabase_realtime_admin` | Realtime | +| `supabase_replication_admin` | Synchronizes Read Replicas | +| `postgres` | Supabase Dashboard and External Tools (e.g., Prisma, SQLAlchemy, PSQL...) | +| Custom roles | External Tools (e.g., Prisma, SQLAlchemy, PSQL...) | Filter by the `parsed.user_name` field to only retrieve logs made by specific users: diff --git a/apps/docs/content/guides/database/prisma.mdx b/apps/docs/content/guides/database/prisma.mdx index af5e4e203da..dbb1606d7d3 100644 --- a/apps/docs/content/guides/database/prisma.mdx +++ b/apps/docs/content/guides/database/prisma.mdx @@ -6,7 +6,7 @@ breadcrumb: 'ORM Quickstarts' hideToc: true --- -This quickly shows how to connect your Prisma application to Supabase Postgres. If you encounter any problems, reference the [Prisma troubleshooting docs](/docs/guides/database/prisma-troubleshooting). +This quickly shows how to connect your Prisma application to Supabase Postgres. If you encounter any problems, reference the [Prisma troubleshooting docs](/docs/guides/database/prisma/prisma-troubleshooting). @@ -17,8 +17,8 @@ If you plan to solely use Prisma instead of the Supabase Data API (PostgREST), t - - In the [SQL Editor](https://supabase.com/dashboard/project/_/sql/new), create a Prisma db-user with full privileges on the public schema. - - This gives you better control over Prisma's access and makes it easier to monitor using Supabase tools like the [Query Performance Dashboard](https://supabase.com/dashboard/project/_/database/query-performance) and [Log Explorer](https://supabase.com/dashboard/project/_/logs/explorer). + - In the [SQL Editor](https://supabase.com/dashboard/project/_/sql/new), create a Prisma DB user with full privileges on the public schema. + - This gives you better control over Prisma's access and makes it easier to monitor using Supabase tools like the [Query Performance Dashboard](https://supabase.com/dashboard/project/_/advisors/query-performance) and [Log Explorer](https://supabase.com/dashboard/project/_/logs/explorer). For security, consider using a [password generator](https://bitwarden.com/password-generator/) for the Prisma role. @@ -45,13 +45,18 @@ If you plan to solely use Prisma instead of the Supabase Data API (PostgREST), t alter default privileges for role postgres in schema public grant all on routines to prisma; alter default privileges for role postgres in schema public grant all on sequences to prisma; ``` + + ```sql + -- alter prisma password if needed + alter user "prisma" with password 'new_password'; + ``` - Create a new prisma Project on your computer + Create a new Prisma Project on your computer @@ -93,11 +98,11 @@ If you plan to solely use Prisma instead of the Supabase Data API (PostgREST), t ```bash yarn init -y - yarn install prisma typescript ts-node @types/node --save-dev + yarn add prisma typescript ts-node @types/node --save-dev - yarn dlx tsc --init + npx tsc --init - yarn dlx prisma init + npx prisma init ``` @@ -118,14 +123,12 @@ If you plan to solely use Prisma instead of the Supabase Data API (PostgREST), t - Visit the [Database Settings](https://supabase.com/dashboard/project/_/settings/database) - ![Session Mode](/docs/img/database/session-mode.png) - Find your Supavisor Session Mode string. It should end with 5432. It will be used in your `.env` file. If you're in an [IPv6 environment](https://github.com/orgs/supabase/discussions/27034) or have the IPv4 Add-On, you can use the direct connection string instead of Supavisor in Session mode. - If you plan on deploying Prisma to a serverless or auto-scaling environment, you'll also need your Supavisor transaction mode string. - ![Transaction Mode](/docs/img/database/transaction-mode.png) - The string is identical to the session mode string but uses port 6543 at the end. @@ -137,7 +140,7 @@ If you plan to solely use Prisma instead of the Supabase Data API (PostgREST), t In your .env file, set the DATABASE_URL variable to your connection string ```text .env # Used for Prisma Migrations and within your application - DATABASE_URL="postgres://[DB-USER].[PROJECT-REF]:[YOUR-PASSWORD]@aws-0-us-east-1.pooler.supabase.com:5432/postgres" + DATABASE_URL="postgres://[DB-USER].[PROJECT-REF]:[PRISMA-PASSWORD]@[DB-REGION].pooler.supabase.com:5432/postgres" ``` Change your string's `[DB-USER]` to `prisma` and add the password you created in step 1 @@ -152,10 +155,10 @@ If you plan to solely use Prisma instead of the Supabase Data API (PostgREST), t Next, create a DIRECT_URL variable in your .env file and assign the connection string that ends with port 5432 to it. ```text .env # Used in your application (use transaction mode) - DATABASE_URL="postgres://[DB-USER].[PROJECT-REF]:[YOUR-PASSWORD]@aws-0-us-east-1.pooler.supabase.com:6543/postgres?pgbouncer=true" + DATABASE_URL="postgres://[DB-USER].[PROJECT-REF]:[PRISMA-PASSWORD]@aws-0-us-east-1.pooler.supabase.com:6543/postgres?pgbouncer=true" # Used for Prisma Migrations (use session mode or direct connection) - DIRECT_URL="postgres://[DB-USER].[PROJECT-REF]:[YOUR-PASSWORD]@aws-0-us-east-1.pooler.supabase.com:5432/postgres" + DIRECT_URL="postgres://[DB-USER].[PROJECT-REF]:[PRISMA-PASSWORD]@aws-0-us-east-1.pooler.supabase.com:5432/postgres" ``` Change both your strings' `[DB-USER]` to `prisma` and then add the password created in step 1 @@ -163,7 +166,7 @@ If you plan to solely use Prisma instead of the Supabase Data API (PostgREST), t postgres://prisma.[PROJECT-REF]... ``` - In your schema.prisma file, edit your datasource db configs to reference your DIRECT_URL + In your schema.prisma file, edit your `datasource db` configs to reference your DIRECT_URL ```text schema.prisma datasource db { provider = "postgresql" @@ -184,7 +187,7 @@ If you plan to solely use Prisma instead of the Supabase Data API (PostgREST), t - + If you have already modified your Supabase database, synchronize it with your migration file. Otherwise create new tables for your database @@ -238,7 +241,7 @@ If you plan to solely use Prisma instead of the Supabase Data API (PostgREST), t ```bash - yarn dlx prisma migrate dev --name first_prisma_migration + npx prisma migrate dev --name first_prisma_migration ``` @@ -266,7 +269,7 @@ If you plan to solely use Prisma instead of the Supabase Data API (PostgREST), t npx prisma db pull ``` - Create a migraton file + Create a migration file ```bash mkdir -p prisma/migrations/0_init_supabase ``` @@ -279,7 +282,7 @@ If you plan to solely use Prisma instead of the Supabase Data API (PostgREST), t --script > prisma/migrations/0_init_supabase/migration.sql ``` - If there are any conflicts, reference [Prisma's official doc](https://www.prisma.io/docs/orm/prisma-migrate/getting-started#work-around-features-not-supported-by-prisma-schema-language) or the [trouble shooting guide](/docs/guides/database/prisma-troubleshooting) for more details + If there are any conflicts, reference [Prisma's official doc](https://www.prisma.io/docs/orm/prisma-migrate/getting-started#work-around-features-not-supported-by-prisma-schema-language) or the [trouble shooting guide](/docs/guides/database/prisma/prisma-troubleshooting) for more details ```bash @@ -292,7 +295,7 @@ If you plan to solely use Prisma instead of the Supabase Data API (PostgREST), t pnpx prisma db pull ``` - Create a migraton file + Create a migration file ```bash mkdir -p prisma/migrations/0_init_supabase ``` @@ -305,7 +308,7 @@ If you plan to solely use Prisma instead of the Supabase Data API (PostgREST), t --script > prisma/migrations/0_init_supabase/migration.sql ``` - If there are any conflicts, reference [Prisma's official doc](https://www.prisma.io/docs/orm/prisma-migrate/getting-started#work-around-features-not-supported-by-prisma-schema-language) or the [trouble shooting guide](/docs/guides/database/prisma-troubleshooting) for more details + If there are any conflicts, reference [Prisma's official doc](https://www.prisma.io/docs/orm/prisma-migrate/getting-started#work-around-features-not-supported-by-prisma-schema-language) or the [trouble shooting guide](/docs/guides/database/prisma/prisma-troubleshooting) for more details ```bash @@ -315,27 +318,27 @@ If you plan to solely use Prisma instead of the Supabase Data API (PostgREST), t ```bash - yarn dlx prisma db pull + npx prisma db pull ``` - Create a migraton file + Create a migration file ```bash mkdir -p prisma/migrations/0_init_supabase ``` Synchronize the migrations ```bash - yarn dlx prisma migrate diff \ + npx prisma migrate diff \ --from-empty \ --to-schema-datamodel prisma/schema.prisma \ --script > prisma/migrations/0_init_supabase/migration.sql ``` - If there are any conflicts, reference [Prisma's official doc](https://www.prisma.io/docs/orm/prisma-migrate/getting-started#work-around-features-not-supported-by-prisma-schema-language) or the [trouble shooting guide](/docs/guides/database/prisma-troubleshooting) for more details + If there are any conflicts, reference [Prisma's official doc](https://www.prisma.io/docs/orm/prisma-migrate/getting-started#work-around-features-not-supported-by-prisma-schema-language) or the [trouble shooting guide](/docs/guides/database/prisma/prisma-troubleshooting) for more details ```bash - yarn dlx prisma migrate resolve --applied 0_init_supabase + npx prisma migrate resolve --applied 0_init_supabase ``` @@ -344,7 +347,7 @@ If you plan to solely use Prisma instead of the Supabase Data API (PostgREST), t bunx prisma db pull ``` - Create a migraton file + Create a migration file ```bash mkdir -p prisma/migrations/0_init_supabase ``` @@ -373,9 +376,9 @@ If you plan to solely use Prisma instead of the Supabase Data API (PostgREST), t - + - Install the prisma client and generate its model + Install the Prisma client and generate its model @@ -400,8 +403,8 @@ If you plan to solely use Prisma instead of the Supabase Data API (PostgREST), t ```sh - yarn install @prisma/client - yarn dlx prisma generate + yarn add @prisma/client + npx prisma generate ``` @@ -414,7 +417,7 @@ If you plan to solely use Prisma instead of the Supabase Data API (PostgREST), t - + Create a index.ts file and run it to test your connection diff --git a/apps/docs/content/guides/database/prisma/prisma-troubleshooting.mdx b/apps/docs/content/guides/database/prisma/prisma-troubleshooting.mdx index 851ea07ef6c..974528af322 100644 --- a/apps/docs/content/guides/database/prisma/prisma-troubleshooting.mdx +++ b/apps/docs/content/guides/database/prisma/prisma-troubleshooting.mdx @@ -27,6 +27,8 @@ connection_string.../postgres?KEY1=VALUE&KEY2=VALUE&KEY3=VALUE # Errors +{/* supa-mdx-lint-disable-next-line Rule001HeadingCase */} + ## ... 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. @@ -162,7 +164,7 @@ An alternative strategy to reference these tables is to duplicate values into Pr
      Show/Hide Details - + ```sql table_in_public -- Create the 'profiles' table in the 'public' schema create table public.profiles ( diff --git a/apps/docs/content/guides/database/query-optimization.mdx b/apps/docs/content/guides/database/query-optimization.mdx index 02a2bd64307..f34e8ebf2bd 100644 --- a/apps/docs/content/guides/database/query-optimization.mdx +++ b/apps/docs/content/guides/database/query-optimization.mdx @@ -34,7 +34,7 @@ limit 10; In this query, there are several parts that indexes could likely help in optimizing the performance: -### `where` Clause: +### `where` clause: The `where` clause filters rows based on certain conditions, and indexing the columns involved can improve this process: @@ -47,18 +47,18 @@ create index idx_customers_sign_up_date on customers (sign_up_date); create index idx_orders_status on orders (status); ``` -### `join` Columns +### `join` columns Indexes on the columns used for joining tables can help Postgres avoid scanning tables in their entirety when connecting tables. -- Indexing and `b.customer_id` would likely improve the performance of the join in this query. +- Indexing `a.id` and `b.customer_id` would likely improve the performance of the join in this query. - Note that if `a.id` is the primary key of the `customers` table it is already indexed ```sql create index idx_orders_customer_id on orders (customer_id); ``` -### `order by` Clause +### `order by` clause Sorting can also be optimized by indexing: diff --git a/apps/docs/content/guides/database/supavisor.mdx b/apps/docs/content/guides/database/supavisor.mdx index 25f755a2d89..1ee0aedd6df 100644 --- a/apps/docs/content/guides/database/supavisor.mdx +++ b/apps/docs/content/guides/database/supavisor.mdx @@ -5,12 +5,13 @@ subtitle: Troubleshooting Supavisor errors Supavisor logs are available under [Pooler Logs](/dashboard/project/_/logs/pooler-logs) in the Dashboard. The following are common errors and their solutions: -| Error Type | Description | Resolution Link | -| ------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------- | -| Max client connections reached | This error happens when the number of connections to Supavisor is more than [the allowed limit of your compute add-on](https://supabase.com/docs/guides/platform/compute-add-ons). | Follow this [guide](https://github.com/orgs/supabase/discussions/22305) to resolve. | -| Connection failed \{:error, :eaddrnotavail} to 'db.xxx.supabase.co':5432 | Supavisor cannot connect to the customer database. This is usually caused if the target database is unable to respond. | N/A | -| Connection failed \{:error, :nxdomain} to 'db.xxx.supabase.co':5432 | Supavisor cannot connect to the customer database. This is usually caused if the target database is unable to respond. | N/A | -| Connection closed when state was authentication | This error happens when either the database doesn’t exist or if the user doesn't have the right credentials. | N/A | -| Subscribe error: \{:error, :worker_not_found} | This log event is emitted when the client tries to connect to the database, but Supavisor does not have the necessary information to route the connection. Try reconnecting to the database as it can take some time for the project information to propagate to Supavisor. | N/A | -| Subscribe error: \{:error, \{:badrpc, \{:error, \{:erpc, :timeout}}}} | This is a timeout error when the communication between different Supavisor nodes takes longer than expected. Try reconnecting to the database. | N/A | -| Terminating with reason :client_termination when state was :busy | This error happens when the client terminates the connection before the connection with the database is completed. | N/A | +| Error Type | Description | Resolution Link | +| ------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------- | +| Max client connections reached | This error happens when the number of connections to Supavisor is more than [the allowed limit of your compute add-on](https://supabase.com/docs/guides/platform/compute-add-ons). | Follow this [guide](https://github.com/orgs/supabase/discussions/22305) to resolve. | +| Connection failed `{:error, :eaddrnotavail}` to 'db.xxx.supabase.co':5432 | Supavisor cannot connect to the customer database. This is usually caused if the target database is unable to respond. | N/A | +| Connection failed `{:error, :nxdomain}` to 'db.xxx.supabase.co':5432 | Supavisor cannot connect to the customer database. This is usually caused if the target database is unable to respond. | N/A | +| Connection closed when state was authentication | This error happens when either the database doesn’t exist or if the user doesn't have the right credentials. | N/A | +| Subscribe error: `{:error, :worker_not_found}` | This log event is emitted when the client tries to connect to the database, but Supavisor does not have the necessary information to route the connection. Try reconnecting to the database as it can take some time for the project information to propagate to Supavisor. | N/A | +| Subscribe error: `{:error, {:badrpc, {:error, {:erpc, :timeout}}}}` | This is a timeout error when the communication between different Supavisor nodes takes longer than expected. Try reconnecting to the database. | N/A | +| Terminating with reason :client_termination when state was :busy | This error happens when the client terminates the connection before the connection with the database is completed. | N/A | +| Error: received invalid response to GSSAPI negotiation: S | This error happens due to `gssencmode` parameter not set to disabled. | Follow this [guide](https://github.com/orgs/supabase/discussions/30173) to resolve. | diff --git a/apps/docs/content/guides/database/tables.mdx b/apps/docs/content/guides/database/tables.mdx index 71a2d2c8fcd..285e33feceb 100644 --- a/apps/docs/content/guides/database/tables.mdx +++ b/apps/docs/content/guides/database/tables.mdx @@ -10,6 +10,8 @@ Tables are where you store your data. Tables are similar to excel spreadsheets. They contain columns and rows. For example, this table has 3 "columns" (`id`, `name`, `description`) and 4 "rows" of data: +{/* supa-mdx-lint-disable Rule003Spelling */} + | `id` | `name` | `description` | | ---- | -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- | | 1 | The Phantom Menace | Two Jedi escape a hostile blockade to find allies and come across a young boy who may bring balance to the Force. | @@ -17,6 +19,8 @@ For example, this table has 3 "columns" (`id`, `name`, `description`) and 4 "row | 3 | Revenge of the Sith | As Obi-Wan pursues a new threat, Anakin acts as a double agent between the Jedi Council and Palpatine and is lured into a sinister plan to rule the galaxy. | | 4 | Star Wars | Luke Skywalker joins forces with a Jedi Knight, a cocky pilot, a Wookiee and two droids to save the galaxy from the Empire's world-destroying battle station. | +{/* supa-mdx-lint-enable Rule003Spelling */} + There are a few important differences from a spreadsheet, but it's a good starting point if you're new to Relational databases. ## Creating tables @@ -91,51 +95,51 @@ Every column is a predefined type. PostgreSQL provides many [default types](http
      Show/Hide default data types -| `Name` | `Aliases` | `Description` | -| --------------------------------- | ----------- | ---------------------------------------------------------------- | -| bigint | int8 | signed eight-byte integer | -| bigserial | serial8 | autoincrementing eight-byte integer | -| bit | | fixed-length bit string | -| bit varying | varbit | variable-length bit string | -| boolean | bool | logical Boolean (true/false) | -| box | | rectangular box on a plane | -| bytea | | binary data (“byte array”) | -| character | char | fixed-length character string | -| character varying | varchar | variable-length character string | -| cidr | | IPv4 or IPv6 network address | -| circle | | circle on a plane | -| date | | calendar date (year, month, day) | -| double precision | float8 | double precision floating-point number (8 bytes) | -| inet | | IPv4 or IPv6 host address | -| integer | int, int4 | signed four-byte integer | -| interval \[ fields \] | | time span | -| json | | textual JSON data | -| jsonb | | binary JSON data, decomposed | -| line | | infinite line on a plane | -| lseg | | line segment on a plane | -| macaddr | | MAC (Media Access Control) address | -| macaddr8 | | MAC (Media Access Control) address (EUI-64 format) | -| money | | currency amount | -| numeric | decimal | exact numeric of selectable precision | -| path | | geometric path on a plane | -| pg_lsn | | PostgreSQL Log Sequence Number | -| pg_snapshot | | user-level transaction ID snapshot | -| point | | geometric point on a plane | -| polygon | | closed geometric path on a plane | -| real | float4 | single precision floating-point number (4 bytes) | -| smallint | int2 | signed two-byte integer | -| smallserial | serial2 | autoincrementing two-byte integer | -| serial | serial4 | autoincrementing four-byte integer | -| text | | variable-length character string | -| time \[ without time zone \] | | time of day (no time zone) | -| time with time zone | timetz | time of day, including time zone | -| timestamp \[ without time zone \] | | date and time (no time zone) | -| timestamp with time zone | timestamptz | date and time, including time zone | -| tsquery | | text search query | -| tsvector | | text search document | -| txid_snapshot | | user-level transaction ID snapshot (deprecated; see pg_snapshot) | -| uuid | | universally unique identifier | -| xml | | XML data | +| `Name` | `Aliases` | `Description` | +| --------------------------------- | ------------- | ---------------------------------------------------------------- | +| `bigint` | `int8` | signed eight-byte integer | +| `bigserial` | `serial8` | autoincrementing eight-byte integer | +| `bit` | | fixed-length bit string | +| `bit varying` | `varbit` | variable-length bit string | +| `boolean` | `bool` | logical Boolean (true/false) | +| `box` | | rectangular box on a plane | +| `bytea` | | binary data (“byte array”) | +| `character` | `char` | fixed-length character string | +| `character varying` | `varchar` | variable-length character string | +| `cidr` | | IPv4 or IPv6 network address | +| `circle` | | circle on a plane | +| `date` | | calendar date (year, month, day) | +| `double precision` | `float8` | double precision floating-point number (8 bytes) | +| `inet` | | IPv4 or IPv6 host address | +| `integer` | `int`, `int4` | signed four-byte integer | +| `interval [ fields ]` | | time span | +| `json` | | textual JSON data | +| `jsonb` | | binary JSON data, decomposed | +| `line` | | infinite line on a plane | +| `lseg` | | line segment on a plane | +| `macaddr` | | MAC (Media Access Control) address | +| `macaddr8` | | MAC (Media Access Control) address (EUI-64 format) | +| `money` | | currency amount | +| `numeric` | `decimal` | exact numeric of selectable precision | +| `path` | | geometric path on a plane | +| `pg_lsn` | | PostgreSQL Log Sequence Number | +| `pg_snapshot` | | user-level transaction ID snapshot | +| `point` | | geometric point on a plane | +| `polygon` | | closed geometric path on a plane | +| `real` | `float4` | single precision floating-point number (4 bytes) | +| `smallint` | `int2` | signed two-byte integer | +| `smallserial` | `serial2` | autoincrementing two-byte integer | +| `serial` | `serial4` | autoincrementing four-byte integer | +| `text` | | variable-length character string | +| `time [ without time zone ]` | | time of day (no time zone) | +| `time with time zone` | `timetz` | time of day, including time zone | +| `timestamp [ without time zone ]` | | date and time (no time zone) | +| `timestamp with time zone` | `timestamptz` | date and time, including time zone | +| `tsquery` | | text search query | +| `tsvector` | | text search document | +| `txid_snapshot` | | user-level transaction ID snapshot (deprecated; see pg_snapshot) | +| `uuid` | | universally unique identifier | +| `xml` | | XML data |
      @@ -300,7 +304,7 @@ supabase ### Bulk data loading When inserting large data sets it's best to use PostgreSQL's [COPY](https://www.postgresql.org/docs/current/sql-copy.html) command. -This loads data directly from a file into a table. There are several file formats available for copying data: text, csv, binary, JSON, etc. +This loads data directly from a file into a table. There are several file formats available for copying data: text, CSV, binary, JSON, etc. For example, if you wanted to load a CSV file into your movies table: @@ -442,12 +446,16 @@ Say we have the following tables from a database of a university: **`students`** +{/* supa-mdx-lint-disable Rule003Spelling */} + | id | name | type | | --- | ---------------- | ------------- | | 1 | Princess Leia | undergraduate | | 2 | Yoda | graduate | | 3 | Anakin Skywalker | graduate | +{/* supa-mdx-lint-enable Rule003Spelling */} + **`courses`** | id | title | code | diff --git a/apps/docs/content/guides/database/testing.mdx b/apps/docs/content/guides/database/testing.mdx index 0561b47a46c..8054ecc2b7f 100644 --- a/apps/docs/content/guides/database/testing.mdx +++ b/apps/docs/content/guides/database/testing.mdx @@ -10,12 +10,6 @@ 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 through the Supabase client - -A good starting point are the tests written for the Supabase client itself. For example, [here](https://github.com/supabase/supabase/tree/master/tests/features/javascript) are the tests for the Supabase JS client written with TypeScript and Jest. - -Keep in mind that the tests (which you'll likely integrate into your CI) need to run with the same DB setup that you're running locally. That means you need to make all changes to the database exclusively through migrations (not manually). - # 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: @@ -24,7 +18,7 @@ You can use the Supabase CLI to test your database. The minimum required version ## Creating a test -Create a tests folder inside the supabase folder: +Create a tests folder inside the `supabase` folder: ```bash mkdir -p ./supabase/tests/database diff --git a/apps/docs/content/guides/database/webhooks.mdx b/apps/docs/content/guides/database/webhooks.mdx index 28297118008..93a17de9ed5 100644 --- a/apps/docs/content/guides/database/webhooks.mdx +++ b/apps/docs/content/guides/database/webhooks.mdx @@ -27,7 +27,7 @@ This video demonstrates how you can create a new customer in Stripe each time a ## Creating a webhook -1. Create a new [Database Webhook](https://supabase.com/dashboard/project/_/database/hooks) in the Dashboard. +1. Create a new [Database Webhook](https://supabase.com/dashboard/project/_/integrations/hooks) in the Dashboard. 1. Give your Webhook a name. 1. Select the table you want to hook into. 1. Select one or more events (table inserts, updates, or deletes) you want to hook into. diff --git a/apps/docs/content/guides/deployment.mdx b/apps/docs/content/guides/deployment.mdx new file mode 100644 index 00000000000..e4d27adf30e --- /dev/null +++ b/apps/docs/content/guides/deployment.mdx @@ -0,0 +1,29 @@ +--- +title: Deployment +--- + +Deploying your app makes it live and accessible to users. Usually, you will deploy an app to at least two environments: a production environment for users and (one or multiple) staging or preview environments for developers. + +Supabase provides several options for environment management and deployment. + +## Environment management + +You can maintain separate development, staging, and production environments for Supabase: + +- **Development**: Develop with a local Supabase stack using the [Supabase CLI](/docs/guides/local-development). +- **Staging**: Use [branching](/docs/guides/deployment/branching) to create staging or preview environments. You can use persistent branches for a long-lived staging setup, or ephemeral branches for short-lived previews (which are often tied to a pull request). +- **Production**: If you have branching enabled, you can use the Supabase GitHub integration to automatically push your migration files when you merge a pull request. Alternatively, you can set up your own continuous deployment pipeline using the Supabase CLI. + + + +See the [self-hosting guides](/docs/guides/self-hosting) for instructions on hosting your own Supabase stack. + + + +## Deployment + +You can automate deployments using: + +- The [Supabase GitHub integration](/dashboard/project/_/settings/integrations) (with branching enabled) +- The [Supabase CLI](/docs/guides/local-development) in your own continuous deployment pipeline +- The [Supabase Terraform provider](/docs/guides/deployment/terraform) diff --git a/apps/docs/content/guides/platform/branching.mdx b/apps/docs/content/guides/deployment/branching.mdx similarity index 69% rename from apps/docs/content/guides/platform/branching.mdx rename to apps/docs/content/guides/deployment/branching.mdx index e7c73754214..8ddcf021ca1 100644 --- a/apps/docs/content/guides/platform/branching.mdx +++ b/apps/docs/content/guides/deployment/branching.mdx @@ -14,9 +14,24 @@ If you understand Git, you already understand Supabase Branching. ## How branching works -Supabase Branching works with Git. You can test changes in a separate, temporary environment without affecting your production setup. When you're ready to ship your changes, merge your branch to update your production instance with the new changes. +- **Separate Environments**: Each branch is a separate environment with its own Supabase instance and API credentials. +- **Git Integration**: Branching works with Git, currently supporting GitHub repositories. +- **Preview Branches**: You can create multiple Preview Branches for testing. +- **Migrations and Seeding**: Branches run migrations from your repository and can seed data using a `seed.sql` file. -You can run multiple Preview Branches for every Supabase project. Branches contain all the Supabase features with their own API credentials. Preview Environments pause automatically after branching.inactivity_period_in_minutes minutes of inactivity. Note that `pg_cron` executions will be impacted by inactivity related pausing. +## Prerequisites + +- **Supabase Project**: You need an existing Supabase project. +- **GitHub Repository**: Your project must be connected to a GitHub repository containing your Supabase directory. + +You can run multiple Preview Branches for every Supabase project. Branches contain all the Supabase features with their own API credentials. + +Preview Environments auto-pause after branching.inactivity_period_in_minutes minutes of inactivity. Upon receiving a new request to your database or REST API, the paused branch will automatically resume to serve the request. The implications of this architecture means + +- `pg_cron` jobs will not execute in an auto-paused database. +- Larger variance in request latency due to database cold starts. + +If you need higher performance guarantees on your Preview Environment, you can switch individual branches to [persistent](/docs/guides/deployment/branching#persistent-branches) so they are not auto-paused. ) for GitLab, BitBucket, and non-Git based Branching. +To manage code changes, your Supabase project must be connected to a Git repository. At this stage, we only support [GitHub](#branching-with-github). If you are interested in other Git providers, join the [discussion](https://github.com/orgs/supabase/discussions/18936) for GitLab, Bitbucket, and non-Git based Branching. ### Branching with GitHub Supabase Branching uses the Supabase GitHub integration to read files from your GitHub repository. With this integration, Supabase watches all commits, branches, and pull requests of your GitHub repository. -In Git, you have a Production Branch (typically this is `main`, `master`, `prod`, etc). This should also be your Supabase project's Production Branch. +You can create a corresponding Preview Branch for any Git branch in your repository. Each time a new Preview Branch is created and configured based on the [`config.toml`](/docs/guides/local-development/cli/config) configuration on this branch, the migrations from the corresponding Git branch are run on the Preview Branch. -You can create a corresponding Preview Branch for any Git branch in your repository. Each time a new Preview Branch is created, the migrations in the Git branch of that Preview Branch are run on the Preview Branch. - -The Preview Branch is also seeded with sample data based on `./supabase/seed.sql`, if that file exists. +The Preview Branch is also [seeded](/docs/guides/local-development/seeding-your-database) with sample data based on `./supabase/seed.sql` by default, if that file exists. Supabase Branching follows the [Trunk Based Development](https://trunkbaseddevelopment.com/) workflow, with one main Production branch and multiple development branches: @@ -60,47 +73,10 @@ Supabase Branching follows the [Trunk Based Development](https://trunkbaseddevel }} /> -### Production branch - -In Git, you have a Production Branch (typically this is `main`, `master`, `prod`, etc). This should also be your Supabase project's Production Branch. - -### Preview branches - -After connecting your Supabase project to one of the supported [Git providers](#git-providers), a corresponding Supabase Preview will be created whenever a new Git branch is created. - -The Git integration can read files from your Git provider, watching every commit and pull request. Each time a commit is pushed with new migrations in the `./supabase/migrations` directory, the migrations are run on the matching Supabase Preview environment: - -New migration files trigger migrations on the preview instance. - -### Data changes - -The Preview Branch is seeded with sample data based on `./supabase/seed.sql`, if that file exists. - -For security reasons, Preview Branches do not contain production data. Future versions of Branching may allow for automated data cloning after we are confident that we can provide safe data masking. +When you merge your Git branch into the production branch, all new migrations will be applied to your Production environment. If you have declared Storage buckets or Edge Functions in `config.toml`, they will also be deployed automatically. All other configurations, including API, Auth, and seed files, will be ignored by default. -### Merging production changes - -When you merge your Git branch into the production branch, all new migrations will be applied to your Production environment. - -### Git providers - -We currently support [GitHub](#branching-with-github). If you are interested in other Git providers, join the [discussion](<(https://github.com/orgs/supabase/discussions/18936)>) for GitLab, BitBucket, and non-Git based Branching. - -## How to use Supabase branching - -Supabase Branching requires a hosted [Git provider](#git-providers). Follow these steps to connect your Supabase project to a Git provider, and enable branching. - ### Preparing your Git repository You can use the [Supabase CLI](/docs/guides/cli) to manage changes inside a local `./supabase` directory: @@ -197,26 +173,22 @@ If your repository doesn't have all the migration files, your production branch - - If you're prompted to join the waitlist, click `Join waitlist`. We're rolling out Branching to users in batches. -
      - +
      -
      - If you have been granted early access, once when clicking `Enable branching` you will see the following dialog: + When clicking `Enable branching` you will see the following dialog:
      - If you don't have the GitHub integration installed, click `Install GitHub Integration`. The integration is required to run migration files and the optional database seed file. + If you don't have the GitHub integration installed, click `Add new project connection`. The integration is required to run migration files and the optional database seed file. You're taken to the GitHub integration page. Click `Install`. @@ -236,9 +208,7 @@ If your repository doesn't have all the migration files, your production branch
      - - - You should now see a popover with the GitHub Connection details shown. + Type in the branch you want to use for production. The name of the branch will be validated to make sure it exists in your GitHub repository. @@ -255,16 +225,42 @@ If your repository doesn't have all the migration files, your production branch - + - -
      -### Create your first preview branch +### Open a pull request + +When you open a pull request on GitHub, the Supabase integration automatically checks for a matching preview branch. If one doesn't exist, it gets created. + +A comment is added to your PR with the deployment status of your preview branch. Statuses are shown separately for Database, Services, and APIs. + +GitHub view of the deployment status of your preview branch + +Every time a new commit is pushed that changes the migration files in `./supabase/migrations`, the new migrations are run against the preview branch. You can check the status of these runs in the comment's Tasks table. + +### Preventing migration failures + +We highly recommend turning on a 'required check' for the Supabase integration. You can do this from your GitHub repository settings. This prevents PRs from being merged when migration checks fail, and stops invalid migrations from being merged into your production branch. + +Check the "Require status checks to pass before merging" option. + +### Manually create a preview branch Preview branches are automatically created for each pull request, but you can also manually create one. @@ -278,15 +274,12 @@ Preview branches are automatically created for each pull request, but you can al
      You can use the GitHub dashboard or command line to create a new branch. In this example, the new branch is called `feat/add-members`.
      - - You can use the GitHub dashboard or command line to create a new branch. In this example, the new branch is called `feat/add-members`. - - In the Supabase dashboard, look for the branch dropdown on the right-hand side of the top bar. It should be set to your production branch by default. Open the dropdown and click `Manage branches`. + In the Supabase dashboard, look for the branch dropdown on the right-hand side of the top bar. It should be set to your production branch by default. Open the dropdown and click [`Manage branches`](/dashboard/project/_/branches).
      @@ -313,7 +306,7 @@ Preview branches are automatically created for each pull request, but you can al
      - +
      @@ -321,8 +314,6 @@ Preview branches are automatically created for each pull request, but you can al -### Make changes to your branch - The Git integration watches for changes in the `supabase` directory. This includes: - All SQL migration files, under the subdirectory `migrations` @@ -339,14 +330,14 @@ You can create new migrations either [locally](#develop-locally) or [remotely](# > -The Supabase CLI provides two options: [manual migrations](https://supabase.com/docs/guides/cli/local-development#database-migrations) and [generated migrations](https://supabase.com/docs/guides/cli/local-development#diffing-changes) using Supabase's local studio and the `supabase db-diff` command. Let's use the latter and push the change to our Preview Branch: +The Supabase CLI provides two options: [manual migrations](https://supabase.com/docs/guides/deployment/database-migrations) and [generated migrations](https://supabase.com/docs/guides/deployment/database-migrations#diffing-changes) using Supabase's local studio and the `supabase db-diff` command. Let's use the latter and push the change to our Preview Branch: - Start supabase locally: + Start Supabase locally: @@ -446,15 +437,15 @@ Dashboard changes aren't automatically reflected in your Git repository. If you' - Make changes to your schema with either with the [Table Editor](https://supabase.com/dashboard/project/_/editor) or the [SQL Editor]((https://supabase.com/dashboard/project/_/sql)). + Make changes to your schema with either the [Table Editor](https://supabase.com/dashboard/project/_/editor) or the [SQL Editor]((https://supabase.com/dashboard/project/_/sql)). - + - If you don't know the password, you must Reset the database password so you know the password. Go to the [database setting page](https://supabase.com/dashboard/project/_/settings/database) and click `Reset database password`. + If you don't know the password, you must reset the database password so you know the password. Go to the [database setting page](https://supabase.com/dashboard/project/_/settings/database) and click `Reset database password`. Save the new password securely for future use. @@ -492,34 +483,6 @@ Dashboard changes aren't automatically reflected in your Git repository. If you' -### Open a pull request - -When you open a pull request on GitHub, the Supabase integration automatically checks for a matching preview branch. If one doesn't exist, it gets created. - -A comment is added to your PR with the deployment status of your preview branch. Statuses are shown separately for Database, Services, and APIs. - -GitHub view of the deployment status of your preview branch - -Every time a new commit is pushed that changes the migration files in `./supabase/migrations`, the new migrations are run against the preview branch. You can check the status of these runs in the comment's Tasks table. - -### Preventing migration failures - -We highly recommend turning on a 'required check' for the Supabase integration. You can do this from your GitHub repository settings. This prevents PRs from being merged when migration checks fail, and stops invalid migrations from being merged into your production branch. - -Check the "Require status checks to pass before merging" option. - ### Disable branching You can disable branching at any time. Navigate to the [Branches](/dashboard/project/_/branches) page, which can be found via the Branches dropdown menu on the top navigation, then click "Manage Branches" in the menu. Click the 'Disable branching' button at the top of the Overview section. @@ -527,7 +490,7 @@ You can disable branching at any time. Navigate to the [Branches](/dashboard/pro ### Persistent branches Persistent branches are the type of branches that will remain active even after the underlying PR is closed. -You can change any branch to be persistent on [Branches](/dashboard/project/_/branches) page by clicking triple dots icon next to the branch you want to modify, and selecting "Switch to persistent". +You can change any branch to be persistent on the [Branches](/dashboard/project/_/branches) page by clicking the triple dots icon next to the branch you want to modify, and selecting "Switch to persistent". All persistent branches can be toggled back to be an ephemeral branch in the exact same way. ## Migration and seeding behavior @@ -536,42 +499,71 @@ Migrations are run in sequential order. Each migration builds upon the previous The preview branch has a record of which migrations have been applied, and only applies new migrations for each commit. This can create an issue when rolling back migrations. +## Branch configuration with remotes + +When Branching is enabled, your `config.toml` settings automatically sync to all ephemeral branches through a one-to-one mapping between your Git and Supabase branches. + +### Basic configuration + +To update configuration for a Supabase branch, simply modify `config.toml` and push to git. The Supabase integration will detect the changes and apply them to the corresponding branch. + +### Remote-specific configuration + +For persistent branches that need specific settings, you can use the `[remotes]` block in your `config.toml`. Each remote configuration must reference an existing project ID. + +Here's an example of configuring a separate seed script for a staging environment: + +```toml +[remotes.staging] +project_id = "your-project-ref" + +[remotes.staging.db.seed] +sql_paths = ["./seeds/staging.sql"] +``` + +Since the `project_id` field must reference an existing branch, you need to create the persistent branch before adding its configuration. Use the CLI to create a persistent branch first: + +```bash +supabase --experimental branches create --persistent +# Do you want to create a branch named develop? [Y/n] +``` + +### Configuration merging + +When merging a PR into a persistent branch, the Supabase integration: + +1. Checks for configuration changes +2. Logs the changes +3. Applies them to the target remote + +If no remote is declared or the project ID is incorrect, the configuration step is skipped. + +### Available configuration options + +All standard configuration options are available in the `[remotes]` block. This includes: + +- Database settings +- API configurations +- Authentication settings +- Edge Functions configuration +- And more + +You can use this to maintain different configurations for different environments while keeping them all in version control. + ### Rolling back migrations You might want to roll back changes you've made in an earlier migration change. For example, you may have pushed a migration file containing schema changes you no longer want. To fix this, push your latest changes, then delete the preview branch in Supabase and reopen it. -The new preview branch is reseeded from your `./supabase/seed.sql` file. Any additional data changes you made on the old preview branch are lost. This is equivalent to running `supabase db reset` locally. All migrations are rerun in sequential order. +The new preview branch is reseeded from your `./supabase/seed.sql` file by default. Any additional data changes you made on the old preview branch are lost. This is equivalent to running `supabase db reset` locally. All migrations are rerun in sequential order. ### Seeding behavior -Your Preview Branches are seeded with sample data from the file `./supabase/seed.sql`. +Your Preview Branches are seeded with sample data using the same as [local seeding behavior](/docs/guides/local-development/seeding-your-database). The database is only seeded once, when the preview branch is created. To rerun seeding, delete the preview branch and recreate it by closing, and reopening your pull request. -## Troubleshooting - -### Migrations are failing - -The GitHub integration automatically checks for new migrations on every commit. It runs any new migrations found in `./supabase/migrations`. - -A migration might fail for various reasons, including invalid SQL statements, and schema conflicts. If a migration fails, the Supabase integration check is shown as failing. - -To check the error message, see the Supabase integration comment on your PR. - -### Schemas drift between preview branches - -If you have multiple preview branches, each preview branch might contain different schema changes. This is similar to Git branches, where each branch might contain different code changes. - -When a preview branch is merged into the production branch, it creates a schema drift between the production branch and the preview branches you haven't merged yet. - -You can solve these conflicts the way you would solve normal Git Conflicts: merge or rebase from your production Git branch to your preview Git branch. Since migrations are applied sequentially, ensure that migration files are timestamped correctly after the rebase. Changes that build on top of earlier changes should always have later timestamps. - -### Changing production branch - -It's not possible to change the Git branch used as the Production branch for Supabase Branching. The only way to change it is to disable and re-enable branching. See [Disable Branching](#disable-branching). - ## Branching and hosting providers Branching works with hosting providers that support preview deployments. @@ -580,12 +572,6 @@ With the Supabase branching integration, you can sync the Git branch used by the ### Vercel - - - The Vercel Integration for Supabase branching is under development. To express your interest, [join the discussion on GitHub discussions](https://github.com/orgs/supabase/discussions/18938). - - - Install the Vercel integration: - From the [Vercel marketplace](https://vercel.com/integrations/supabase) or @@ -597,39 +583,63 @@ For branching to work with Vercel, you also need the [Vercel GitHub integration] -Supabase automatically updates your Vercel project with the correct environment variables for the corresponding preview branches. +And make sure you have [connected](/dashboard/org/_/integrations) your Supabase project to your Vercel project. -### Netlify +Supabase automatically updates your Vercel project with the correct environment variables for the corresponding preview branches. The synchronization happens at the time of Pull Request being opened, not at the time of branch creation. -A Netlify integration is under consideration. If you're interested in branching with Netlify, [join the GitHub discussion](https://github.com/orgs/supabase/discussions/18938). +As branching integration is tied to the Preview Deployments feature in Vercel, there are possible race conditions between Supabase setting correct variables, and Vercel running a deployment process. Because of that, Supabase is always automatically re-deploying the most recent deployment of the given pull request. + +## Other Git providers + +There are multiple alternative Git providers under consideration. If you're interested in branching for GitLab, Bitbucket, or some other provider, [join the GitHub discussion](https://github.com/orgs/supabase/discussions/18938). ## Alternatives to branching -If you don't turn on branching, your Supabase project continues to work as a single branch, on a single instance. You have a single set of API keys for each project, and no preview instances are created. It's the Git equivalent of working directly on the `main` branch. +Under the hood, you can see Supabase branching as a way to programmatically "duplicate" your Supabase project via git flow. This allows spawning a new configured (via [`config.toml`](/docs/guides/local-development/cli/config)) and seeded instance of the database and the adjacent Supabase services (buckets, edge functions, etc.). -If you prefer not to use branching, you can manage your environments and tests in other ways: +1. A new project is deployed on behalf of the user on the Supabase side as the new "branch" if it doesn't already exist. This includes the database, storage, edge-function, and all Supabase-related services. +2. The branch is cloned and the new project is configured based on the [`config.toml`](/docs/guides/local-development/cli/config) committed into this project branch. +3. Migrations are applied and seeding scripts are run (the first time) for this branch. -1. ##### Host a project per environment, and test against a staging project +You can make a similar setup with a distinct project for each environment. Or just have two environments, the localhost and the production one. - Create multiple projects on Supabase with the same schema. Use one project as a staging environment to test any changes. Then migrate tested changes to the production project. +## Pricing -2. ##### Host a single production project, and test locally - - Create a single project to host your production instance. Test any changes locally, then run the migrations against your hosted production project. - -You can also combine both strategies to perform both local and staging tests. - -## Early access pricing - -Branching is available on the Pro Plan and above for Early Access users. The price during Early Access is: +Branching is available on the Pro Plan and above. The price is: - Each Preview branch costs $0.32 per day - Each Preview branch is billed until it is removed -Prices listed are for Early Access and are subject to change. +## Troubleshooting + +### Rolling back migrations + +You might want to roll back changes you've made in an earlier migration change. For example, you may have pushed a migration file containing schema changes you no longer want. + +To fix this, push the latest changes, then delete the preview branch in Supabase and reopen it. + +The new preview branch is reseeded from the `./supabase/seed.sql` file by default. Any additional data changes made on the old preview branch are lost. This is equivalent to running `supabase db reset` locally. All migrations are rerun in sequential order. + +### Deployment failures + +A deployment might fail for various reasons, including invalid SQL statements and schema conflicts in migrations, errors within the `config.toml` config, or something else. + +To check the error message, see the Supabase workflow run for your branch under the [View logs](/dashboard/project/_/branches) section. + +### Schema drift between preview branches + +If multiple preview branches exist, each preview branch might contain different schema changes. This is similar to Git branches, where each branch might contain different code changes. + +When a preview branch is merged into the production branch, it creates a schema drift between the production branch and the preview branches that haven't been merged yet. + +These conflicts can be resolved in the same way as normal Git Conflicts: merge or rebase from the production Git branch to the preview Git branch. Since migrations are applied sequentially, ensure that migration files are timestamped correctly after the rebase. Changes that build on top of earlier changes should always have later timestamps. + +### Changing production branch + +It's not possible to change the Git branch used as the Production branch for Supabase Branching. The only way to change it is to disable and re-enable branching. See [Disable Branching](#disable-branching). ## Feedback -Supabase branching is a new and exciting new part of the Supabase development ecosystem. We're monitoring its success and open to any feedback. +Supabase branching is a new and exciting part of the Supabase development ecosystem. Feedback is welcome. You can join the [conversation over in GitHub discussions](https://github.com/orgs/supabase/discussions/18937). diff --git a/apps/docs/content/guides/deployment/database-migrations.mdx b/apps/docs/content/guides/deployment/database-migrations.mdx new file mode 100644 index 00000000000..2502d360ae2 --- /dev/null +++ b/apps/docs/content/guides/deployment/database-migrations.mdx @@ -0,0 +1,321 @@ +--- +id: 'database-migrations' +title: 'Database Migrations' +description: 'How to manage schema migrations for your Supabase project.' +subtitle: 'How to manage schema migrations for your Supabase project.' +video: 'https://www.youtube-nocookie.com/v/Kx5nHBmIxyQ' +tocVideo: 'Kx5nHBmIxyQ' +--- + +Database migrations are SQL statements that create, update, or delete your existing database schemas. They are a common way of tracking changes to your database over time. + +## Schema migrations + +For this guide, we'll create a table called `employees` and see how we can make changes to it. + +You will need to [install](/docs/guides/local-development#quickstart) the Supabase CLI and start the local development stack. + + + + + + To get started, generate a [new migration](/docs/reference/cli/supabase-migration-new) to store the SQL needed to create our `employees` table. + + + + +```bash Terminal +supabase migration new create_employees_table +``` + + + + + + + + + + + This creates a new migration file in supabase/migrations directory. + + To that file, add the SQL to create this `employees` table. + + + + +```sql supabase/migrations/_create_employees_table.sql +create table if not exists employees ( + id bigint primary key generated always as identity, + name text not null, + email text, + created_at timestamptz default now() +); +``` + + + + + + + + + + + Run this migration to create the `employees` table. + + Now you can visit your new `employees` table in the local Dashboard. + + + + +```bash Terminal +supabase migration up +``` + + + + + + + + + + + Next, modify your `employees` table by adding a column for `department`. + + + + +```bash Terminal +supabase migration new add_department_column +``` + + + + + + + + + + + To that new migration file, add the SQL to create a new `department` column. + + + + +```sql supabase/migrations/_add_department_column.sql +alter table if exists public.employees +add department text default 'Hooli'; +``` + + + + + + + + + + + Run this migration to update your existing `employees` table. + + + + +```bash Terminal +supabase migration up +``` + + + + + + +Finally, you should see the `department` column added to your `employees` table in the local Dashboard. + + + +View the [complete code](https://github.com/supabase/supabase/tree/master/examples/database/employees) for this example on GitHub. + + + +### Seeding data + +Now that you are managing your database with migrations scripts, it would be great have some seed data to use every time you reset the database. + + + + + + Create a seed script in supabase/seed.sql. + + To that file, add the SQL to insert data into your `employees` table. + + + + +```sql supabase/seed.sql +insert into public.employees + (name) +values + ('Erlich Bachman'), + ('Richard Hendricks'), + ('Monica Hall'); +``` + + + + + + + + + + + Reset your database to reapply migrations and populate with seed data. + + + + +```bash Terminal +supabase db reset +``` + + + + + + +You should now see the `employees` table, along with your seed data in the Dashboard! All of your database changes are captured in code, and you can reset to a known state at any time, complete with seed data. + +### Diffing changes + +This workflow is great if you know SQL and are comfortable creating tables and columns. If not, you can still use the Dashboard to create tables and columns, and then use the CLI to diff your changes and create migrations. + + + + + + Create a new table called `cities`, with columns `id`, `name` and `population`. + + Then generate a [schema diff](/docs/reference/cli/supabase-db-diff). + + + + +```bash Terminal +supabase db diff -f create_cities_table +``` + + + + + + + + + + + A new migration file is created for you. + + Alternately, you can copy the table definitions directly from the Table Editor. + + + + +```sql supabase/migrations/_create_cities_table.sql +create table "public"."cities" ( + "id" bigint primary key generated always as identity, + "name" text, + "population" bigint +); +``` + + + + + + + + + + + Test your new migration file by resetting your local database. + + + + +```bash Terminal +supabase db reset +``` + + + + + + +The last step is deploying these changes to a live Supabase project. + +## Deploy your project + +You've been developing your project locally, making changes to your tables via migrations. It's time to deploy your project to the Supabase Platform and start scaling up to millions of users! + +Head over to [Supabase](https://supabase.com/dashboard) and create a new project to deploy to. + + + + + + [Login](/docs/reference/cli/supabase-login) to the Supabase CLI using an auto-generated Personal Access Token. + + + + +```bash Terminal +supabase login +``` + + + + + + + + + + + [Link](/docs/reference/cli/supabase-link) to your remote project by selecting from the on-screen prompt. + + + + +```bash Terminal +supabase link +``` + + + + + + + + + + + [Push](/docs/reference/cli/supabase-db-push) your migrations to the remote database. + + + + +```bash Terminal +supabase db push +``` + + + + + + +Visiting your live project on [Supabase](https://supabase.com/dashboard/project/_), you'll see a new `employees` table, complete with the `department` column you added in the second migration above. diff --git a/apps/docs/content/guides/platform/going-into-prod.mdx b/apps/docs/content/guides/deployment/going-into-prod.mdx similarity index 97% rename from apps/docs/content/guides/platform/going-into-prod.mdx rename to apps/docs/content/guides/deployment/going-into-prod.mdx index dba01cb4e90..97d8ea94fb3 100644 --- a/apps/docs/content/guides/platform/going-into-prod.mdx +++ b/apps/docs/content/guides/deployment/going-into-prod.mdx @@ -42,7 +42,7 @@ After developing your project and deciding it's Production Ready, you should run - Tools like [k6](https://k6.io/) can simulate traffic from many different users. - Upgrade your database if you require more resources. If you need anything beyond what is listed, contact enterprise@supabase.io. - If you are expecting a surge in traffic (for a big launch) and are on a Team or Enterprise Plan, [contact support](https://supabase.com/dashboard/support/new) with more details about your launch and we'll help keep an eye on your project. -- If you expect your database size to be > 4 GB, [enable](https://supabase.com/dashboard/project/_/settings/addons?panel=pitr) the Point in Time Recovery (PITR) addon. Daily backups can take up resources from your database when the backup is in progress. PITR is more resource efficient, since only the changes to the database are backed up. +- If you expect your database size to be > 4 GB, [enable](https://supabase.com/dashboard/project/_/settings/addons?panel=pitr) the Point in Time Recovery (PITR) add-on. Daily backups can take up resources from your database when the backup is in progress. PITR is more resource efficient, since only the changes to the database are backed up. - Check and review issues in your database using [Performance Advisor](https://supabase.com/dashboard/project/_/database/performance-advisor). ## Availability @@ -75,7 +75,7 @@ After developing your project and deciding it's Production Ready, you should run | ------------------------------------------------ | -------------------------------------------------------------- | ------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | All endpoints that send emails | `/auth/v1/signup` `/auth/v1/recover` `/auth/v1/user`[^1] | Sum of combined requests | As of 3 Sep 2024, this has been updated to auth.rate_limits.email.inbuilt_smtp_per_hour.value emails per hour. You can only change this with your own [custom SMTP setup](/docs/guides/auth/auth-smtp). | | All endpoints that send One-Time-Passwords (OTP) | `/auth/v1/otp` | Sum of combined requests | Defaults to 360 OTPs per hour. Is customizable. | -| Send OTPs or magiclinks | `/auth/v1/otp` | Last request | Defaults to 60 seconds window before a new request is allowed. Is customizable. | +| Send OTPs or magic links | `/auth/v1/otp` | Last request | Defaults to 60 seconds window before a new request is allowed. Is customizable. | | Signup confirmation request | `/auth/v1/signup` | Last request | Defaults to 60 seconds window before a new request is allowed. Is customizable. | | Password Reset Request | `/auth/v1/recover` | Last request | Defaults to 60 seconds window before a new request is allowed. Is customizable. | | Verification requests | `/auth/v1/verify` | IP Address | 360 requests per hour (with bursts up to 30 requests) | @@ -97,7 +97,7 @@ After developing your project and deciding it's Production Ready, you should run - When working with enterprise systems, email scanners may scan and make a `GET` request to the reset password link or sign up link in your email. Since links in Supabase Auth are single use, a user who opens an email post-scan to click on a link will receive an error. To get around this problem, consider altering the email template to replace the original magic link with a link to a domain you control. The domain can present the user with a "Sign-in" button which redirect the user to the original magic link URL when clicked. -- When using a custom SMTP service, some services might have link tracking enabled which may overwrite or malform the email confirmation links sent by Supabase Auth. To prevent this from happening, we recommend that you disable link tracking when using a custom SMTP service. +- When using a custom SMTP service, some services might have link tracking enabled which may overwrite or disform the email confirmation links sent by Supabase Auth. To prevent this from happening, we recommend that you disable link tracking when using a custom SMTP service. ## Next steps diff --git a/apps/docs/content/guides/cli/managing-environments.mdx b/apps/docs/content/guides/deployment/managing-environments.mdx similarity index 98% rename from apps/docs/content/guides/cli/managing-environments.mdx rename to apps/docs/content/guides/deployment/managing-environments.mdx index bde4fefce16..b4b90e37676 100644 --- a/apps/docs/content/guides/cli/managing-environments.mdx +++ b/apps/docs/content/guides/deployment/managing-environments.mdx @@ -146,7 +146,7 @@ Commit the new migration script to git and you are ready to deploy. -Alternatively, you may pass in the `--use-migra` experimental flag to generate a more concise migration using [migra](https://github.com/djrobstep/migra). +Alternatively, you may pass in the `--use-migra` experimental flag to generate a more concise migration using [`migra`](https://github.com/djrobstep/migra). Without the `-f` file flag, the output is written to stdout by default. @@ -360,7 +360,7 @@ Once pushed, check that the migration version is up to date for both local and r supabase migration list ``` -### Permission denied on db pull +### Permission denied on `db pull` If you have been using Supabase hosted projects for a long time, you might encounter the following permission error when executing `db pull`. @@ -378,7 +378,7 @@ grant all on all functions in schema graphql to postgres, anon, authenticated, s grant all on all sequences in schema graphql to postgres, anon, authenticated, service_role; ``` -### Permission denied on db push +### Permission denied on `db push` If you created a table through Supabase dashboard, and your new migration script contains `ALTER TABLE` statements, you might run into permission error when applying them on staging or production databases. diff --git a/apps/docs/content/guides/platform/maturity-model.mdx b/apps/docs/content/guides/deployment/maturity-model.mdx similarity index 87% rename from apps/docs/content/guides/platform/maturity-model.mdx rename to apps/docs/content/guides/deployment/maturity-model.mdx index b224f6c04d9..6caddaaf96c 100644 --- a/apps/docs/content/guides/platform/maturity-model.mdx +++ b/apps/docs/content/guides/deployment/maturity-model.mdx @@ -8,7 +8,7 @@ Supabase is great for building something very fast _and_ for scaling up. However ## Prototyping -The Dashboard is a quick and easy tool for building applications while you are prototyping. That said, we strongly recommend using [Migrations](/docs/guides/getting-started/local-development#database-migrations) to manage your database changes. You can use our CLI to [capture any changes](/docs/reference/cli/supabase-db-diff) you have made on the Dashboard so that you can commit them a version control system, like git. +The Dashboard is a quick and easy tool for building applications while you are prototyping. That said, we strongly recommend using [Migrations](/docs/guides/deployment/database-migrations) to manage your database changes. You can use our CLI to [capture any changes](/docs/reference/cli/supabase-db-diff) you have made on the Dashboard so that you can commit them a version control system, like git. ## Collaborating @@ -16,7 +16,7 @@ As soon as you start collaborating with team members, all project changes should Resources: -- [Database migrations](/docs/guides/getting-started/local-development#database-migrations) +- [Database migrations](/docs/guides/deployment/database-migrations) - [Managing access on the Dashboard](/docs/guides/platform/access-control) - [PGAudit for Postgres](/docs/guides/database/extensions/pgaudit) @@ -33,7 +33,7 @@ Once your application is live, you should never change your database using the D Resources: -- [Database migrations](/docs/guides/getting-started/local-development#database-migrations) +- [Database migrations](/docs/guides/deployment/database-migrations) - [Managing access on the Dashboard](/docs/guides/platform/access-control) - [PGAudit for Postgres](/docs/guides/database/extensions/pgaudit) - [Managing environments](/docs/guides/cli/managing-environments) diff --git a/apps/docs/content/guides/platform/shared-responsibility-model.mdx b/apps/docs/content/guides/deployment/shared-responsibility-model.mdx similarity index 96% rename from apps/docs/content/guides/platform/shared-responsibility-model.mdx rename to apps/docs/content/guides/deployment/shared-responsibility-model.mdx index d078699a547..a4d8ed0fb54 100644 --- a/apps/docs/content/guides/platform/shared-responsibility-model.mdx +++ b/apps/docs/content/guides/deployment/shared-responsibility-model.mdx @@ -61,7 +61,7 @@ Supabase offers a lot of opportunities for flexibly integrating with third-party - Calls to external APIs within Postgres functions or triggers - Calls to external APIs within Edge Functions -You are free to use and integrate with any service, but you're also responsible for ensuring that the performance, availability, and security of the services you use match up with your application's requirements. We do not monitor for outages or performance issues within integrations with third-party services. Depending on the implementation, an issue with such an integration could also result in performance degradataion or an outage for your Supabase project. +You are free to use and integrate with any service, but you're also responsible for ensuring that the performance, availability, and security of the services you use match up with your application's requirements. We do not monitor for outages or performance issues within integrations with third-party services. Depending on the implementation, an issue with such an integration could also result in performance degradation or an outage for your Supabase project. If your application architecture relies on such integrations, you should monitor the relevant logs and metrics to ensure optimal performance. @@ -86,6 +86,7 @@ We recommend reviewing and applying the recommendations offered in our [Producti You can use Supabase to store and process Protected Health Information (PHI). You are responsible for the following - Signing a Business Associate Agreement (BAA) with Supabase. Submit a [HIPAA add-on request](https://forms.supabase.com/hipaa2) to get started. You will need to be at least on the [Team Plan](https://supabase.com/pricing) to sign a BAA with us. +- [Marking specific projects as HIPAA projects](/docs/guides/platform/hipaa-projects) and addressing security issues raised by the advisor. - Ensuring [MFA is enabled](/docs/guides/platform/multi-factor-authentication) on all Supabase accounts. - 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). @@ -93,7 +94,6 @@ You can use Supabase to store and process Protected Health Information (PHI). Yo - Disabling data sharing for [Supabase AI editor](https://supabase.com/dashboard/org/_/general) in our dashboard. - Specifically, "_Opt-in to sending anonymous data to OpenAI_" should be disabled (Opt-out). - 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 using [Edge functions](/docs/guides/functions) or [Fly Postgres](/docs/guides/platform/fly-postgres) to process PHI. -- Not using [branching](/docs/guides/platform/branching) to process PHI. +- Not using [Edge functions](/docs/guides/functions) to process PHI. - 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. diff --git a/apps/docs/content/guides/functions/ai-models.mdx b/apps/docs/content/guides/functions/ai-models.mdx index 60b9df79bcd..dcf300e1d04 100644 --- a/apps/docs/content/guides/functions/ai-models.mdx +++ b/apps/docs/content/guides/functions/ai-models.mdx @@ -165,7 +165,7 @@ curl --get "http://localhost:54321/functions/v1/ollama-test" \ -Follow the [Llamafile Quickstart](https://github.com/Mozilla-Ocho/llamafile?tab=readme-ov-file#quickstart) to download an run a llamafile locally on your machine. +Follow the [Llamafile Quickstart](https://github.com/Mozilla-Ocho/llamafile?tab=readme-ov-file#quickstart) to download an run a Llamafile locally on your machine. Since Llamafile provides an OpenAI API compatible server, you can either use it with `@supabase/functions-js` or with the official OpenAI Deno SDK. @@ -190,7 +190,7 @@ Create a new function with the following code supabase functions new llamafile-test ``` - + Note that the model parameter doesn't have any effect here! The model depends on which Llamafile is currently running! @@ -246,7 +246,7 @@ Create a new function with the following code supabase functions new llamafile-test ``` - + Note that the model parameter doesn't have any effect here! The model depends on which Llamafile is currently running! diff --git a/apps/docs/content/guides/functions/auth.mdx b/apps/docs/content/guides/functions/auth.mdx index 639e667ff34..7b97b497278 100644 --- a/apps/docs/content/guides/functions/auth.mdx +++ b/apps/docs/content/guides/functions/auth.mdx @@ -16,12 +16,15 @@ import { createClient } from 'jsr:@supabase/supabase-js@2' Deno.serve(async (req: Request) => { - const authHeader = req.headers.get('Authorization')! const supabaseClient = createClient( Deno.env.get('SUPABASE_URL') ?? '', Deno.env.get('SUPABASE_ANON_KEY') ?? '', - { global: { headers: { Authorization: authHeader } } } - ) + ); + + // Get the session or user object + const authHeader = req.headers.get('Authorization')!; + const token = authHeader.replace('Bearer ', ''); + const { data } = await supabaseClient.auth.getUser(token); }) ``` @@ -68,11 +71,13 @@ Deno.serve(async (req: Request) => { const supabaseClient = createClient( Deno.env.get('SUPABASE_URL') ?? '', Deno.env.get('SUPABASE_ANON_KEY') ?? '', - { global: { headers: { Authorization: req.headers.get('Authorization')! } } } - ) + ); - // Database queries will have RLS policies enforced - const { data, error } = await supabaseClient.from('profiles').select('*') + // Get the session or user object + const authHeader = req.headers.get('Authorization')!; + const token = authHeader.replace('Bearer ', ''); + const { data: userData } = await supabaseClient.auth.getUser(token); + const { data, error } = await supabaseClient.from('profiles').select('*'); return new Response(JSON.stringify({ data }), { headers: { 'Content-Type': 'application/json' }, diff --git a/apps/docs/content/guides/functions/background-tasks.mdx b/apps/docs/content/guides/functions/background-tasks.mdx new file mode 100644 index 00000000000..9480c2342cd --- /dev/null +++ b/apps/docs/content/guides/functions/background-tasks.mdx @@ -0,0 +1,75 @@ +--- +id: 'function-background-tasks' +title: 'Background Tasks' +description: 'How to run background tasks in an Edge Function outside of the request handler' +subtitle: 'How to run background tasks in an Edge Function outside of the request handler' +--- + +Edge Function instances can process background tasks outside of the request handler. Background tasks are useful for asynchronous operations like uploading a file to Storage, updating a database, or sending events to a logging service. You can respond to the request immediately and leave the task running in the background. + +### How it works + +You can use `EdgeRuntime.waitUntil(promise)` to explicitly mark background tasks. The Function instance continues to run until the promise provided to `waitUntil` completes. + +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). + +You can listen to the `beforeunload` event handler to be notified when Function invocation is about to be shut down. + +### Example + +Here's an example of using `EdgeRuntime.waitUntil` to run a background task and using `beforeunload` event to be notified when the instance is about to be shut down. + +```ts +async function longRunningTask() { + // do work here +} + +// Mark the longRunningTask's returned promise as a background task. +// note: we are not using await because we don't want it to block. +EdgeRuntime.waitUntil(longRunningTask()) + +// Use beforeunload event handler to be notified when function is about to shutdown +addEventListener('beforeunload', (ev) => { + console.log('Function will be shutdown due to', ev.detail?.reason) + + // save state or log the current progress +}) + +// Invoke the function using a HTTP request. +// This will start the background task +Deno.serve(async (req) => { + return new Response('ok') +}) +``` + +### Starting a background task in the request handler + +You can call `EdgeRuntime.waitUntil` in the request handler too. This will not block the request. + +```ts +async function fetchAndLog(url: string) { + const response = await fetch(url) + console.log(response) +} + +Deno.serve(async (req) => { + // this will not block the request, + // instead it will run in the background + EdgeRuntime.waitUntil(fetchAndLog('https://httpbin.org/json')) + + return new Response('ok') +}) +``` + +### Testing background tasks locally + +When testing Edge Functions locally with Supabase CLI, the instances are terminated automatically after a request is completed. This will prevent background tasks from running to completion. + +To prevent that, you can update the `supabase/config.toml` with the following settings: + +```toml +[edge_runtime] +policy = "per_worker" +``` + +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`. diff --git a/apps/docs/content/guides/functions/compression.mdx b/apps/docs/content/guides/functions/compression.mdx index 77964769461..c735106fb99 100644 --- a/apps/docs/content/guides/functions/compression.mdx +++ b/apps/docs/content/guides/functions/compression.mdx @@ -43,7 +43,7 @@ Deno.serve(async (req) => { }) ``` - + Edge functions have a runtime memory limit of 150MB. Overly large compressed payloads may result in an out-of-memory error. diff --git a/apps/docs/content/guides/functions/connect-to-postgres.mdx b/apps/docs/content/guides/functions/connect-to-postgres.mdx index 12478874d5c..22b0ada259b 100644 --- a/apps/docs/content/guides/functions/connect-to-postgres.mdx +++ b/apps/docs/content/guides/functions/connect-to-postgres.mdx @@ -16,7 +16,7 @@ The `supabase-js` client is a great option for connecting to your Supabase datab ```ts index.ts import { createClient } from 'jsr:@supabase/supabase-js@2' -Deno.serve(async (_req) => { +Deno.serve(async (req) => { try { const supabase = createClient( Deno.env.get('SUPABASE_URL') ?? '', diff --git a/apps/docs/content/guides/functions/dart-edge.mdx b/apps/docs/content/guides/functions/dart-edge.mdx index 7c50737ddd5..3f022d5c988 100644 --- a/apps/docs/content/guides/functions/dart-edge.mdx +++ b/apps/docs/content/guides/functions/dart-edge.mdx @@ -2,169 +2,14 @@ id: 'functions-dart-edge' title: 'Dart Edge' description: 'Write your functions using Dart.' -tocVideo: '53jhgrDONW4' --- -[Dart Edge](https://docs.dartedge.dev/), a project built and maintained by [Invertase](https://invertase.io/), enables Dart code to be run on different edge environments. This guide will walk you through how you can start using Dart Edge to write your backend code using Dart and run it on Supabase. - -Dart Edge is an experimental project - it's probably not ready for production usage unless you're okay with living on the 'edge'. Open issues on [Dart Edge repo](https://github.com/invertase/dart_edge) if you encounter any issues. +Please be aware that the Dart Edge project is currently not actively maintained due to numerous breaking changes in Dart's development of (WASM) support. -## Prerequisites +[Dart Edge](https://docs.dartedge.dev/) is an experimental project that enables you to write Supabase Edge Functions using Dart. It's built and maintained by [Invertase](https://invertase.io/). -Make sure you have [the edge CLI](https://docs.dartedge.dev/#getting-started) installed in addition to [the Supabase CLI](https://supabase.com/docs/guides/cli#installation). - -```bash -# install the edge CLI -dart pub global activate edge -``` - -## Setting up your first Dart Edge project - -Create a new Dart Edge project using the `new` command on the edge CLI. This will create a familiar boilerplate for your Dart Edge project such as `main.dart` and `pubspec.yaml` file. - -```bash -edge new supabase_functions new_project -``` - -`cd` into the project and initialize a new Supabase project inside the project you just created. - -```bash -cd new_project -supabase init -``` - -Run `pub get` to install the dependencies. - -```bash -dart pub get -``` - -Open the `lib/main.dart` and look at the initial template. - -```dart -void main() { - SupabaseFunctions(fetch: (request) { - return Response("Hello from Supabase Edge Functions!"); - }); -} -``` - -`SupabaseFunctions` class has a `fetch` handler, which is expected to return a `Response` object, and has a `request` parameter, which contains information about the request such as body and headers. - -## Supporting multiple edge functions - -By default, Dart Edge will create a single `main.dart` file in your project, which will be compiled into a function called `dart_edge`. -You can override this behavior and create multiple functions by creating an `edge.yaml` file in the root of your project. -Specify how to map which of your dart files should compile to what Supabase edge functions using the `functions` option. - -```yaml -supabase: - functions: - api_handler: 'lib/api.dart' - webhook_handler: 'lib/webhooks.dart' -``` - -## Using Supabase client - -Now let’s try to interact with our Supabase database. We will start by installing the dependencies. - -### Add dependencies - -Add [`supabase`](https://pub.dev/packages/supabase) to interact with Supabase service. - -```bash -dart pub add supabase -``` - -We also need to use a special HTTP client to HTTP requests in the edge environment. - -```bash -dart pub add edge_http_client -``` - -### Initialize `SupabaseClient` - -At this point, you can initialize a `SupabaseClient` by passing the [EdgeHttpClient](https://pub.dev/packages/edge_http_client). We can access the Supabase credentials through the [environment variables](https://supabase.com/docs/guides/functions/secrets) using `Deno.env.get()` method. Note that [Supabase credentials are available by default](https://supabase.com/docs/guides/functions/secrets), so no configurations are necessary. - -Now we have the following sample functions code to query Supabase database using the Supabase client. - -```dart -import 'dart:convert'; - -import 'package:supabase_functions/supabase_functions.dart'; -import 'package:edge_http_client/edge_http_client.dart'; -import 'package:supabase/supabase.dart'; - -void main() { - final supabase = SupabaseClient( - Deno.env.get('SUPABASE_URL')!, - Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!, // Use service role key to bypass RLS - httpClient: EdgeHttpClient(), - ); - - SupabaseFunctions(fetch: (request) async { - // You can query `public.users` table for example. - final List users = await supabase.from('users').select().limit(10); - return Response.json(users); - }); -} -``` - -## Run the Functions - -### Run locally - -Create a `edge.yaml` file if you don't have one yet in the root of your project. Then, add the following configuration to specify the path to your Supabase project. - -```yaml -supabase: - project_path: './supabase' -``` - -Also, currently Dart Edge doesn't work with the latest versions of Flutter. If you do have the latest version of Flutter on your machine, you can use [Flutter Version Management (FVM)](https://fvm.app/) to use an older version of Flutter for your Dart Edge project. - -```bash -fvm use 3.7.12 -f -``` - -You can read more about how to configure FVM for your project in the [FVM documentation](https://fvm.app/documentation/getting-started). - -Run the following command to compile your Dart code. This command will start a watcher that will recompile anytime you make changes to your code. - -```bash -edge build supabase_functions --dev - -# Or with FVM -fvm dart pub run edge build supabase_functions --dev -``` - -With docker running, start your local Supabase with the following command - -```bash -supabase start -``` - -While you have the above code running, open another terminal and run the following to start the Supabase local development environment. - -```bash -supabase functions serve dart_edge --no-verify-jwt -``` - -You should be able to access your local function here: - -[http://localhost:54321/functions/v1/dart_edge](http://localhost:54321/functions/v1/dart_edge) - -### Deploy to the Edge - -Run the following commands to compile and deploy your function on Supabase Edge functions. - -```bash -edge build supabase_functions -supabase functions deploy dart_edge -``` - -You will be asked to provide the project reference of your Supabase instance to which you want to deploy the function. You can [link your local project to your Supabase instance](https://supabase.com/docs/guides/getting-started/local-development#link-your-project) to avoid having to provide the Supabase reference every time you deploy. +For detailed information on how to set up and use Dart Edge with Supabase, please refer to the [official Dart Edge documentation for Supabase](https://invertase.docs.page/dart_edge/platform/supabase). diff --git a/apps/docs/content/guides/functions/dependencies.mdx b/apps/docs/content/guides/functions/dependencies.mdx new file mode 100644 index 00000000000..d13bd7fa187 --- /dev/null +++ b/apps/docs/content/guides/functions/dependencies.mdx @@ -0,0 +1,216 @@ +--- +id: 'functions-import-maps' +title: 'Managing dependencies' +description: 'Managing packages and dependencies.' +subtitle: 'Managing packages and dependencies.' +tocVideo: 'ILr3cneZuFk' +--- + +## Importing dependencies + +Supabase Edge Functions support several ways to import dependencies: + +- JavaScript modules from npm (https://docs.deno.com/examples/npm/) +- Built-in [Node APIs](https://docs.deno.com/runtime/manual/node/compatibility) +- Modules published to [JSR](https://jsr.io/) or [deno.land/x](https://deno.land/x) + +### NPM modules + +You can import npm modules using the `npm:` specifier: + +```ts +import { createClient } from 'npm:@supabase/supabase-js@2' +``` + +### Node.js built-ins + +For Node.js built-in APIs, use the `node:` specifier: + +```ts +import process from 'node:process' +``` + +Learn more about npm specifiers and Node built-in APIs in [Deno's documentation](https://docs.deno.com/runtime/manual/node/npm_specifiers). + +### JSR + +You can import JS modules published to [JSR](https://jsr.io/) (e.g.: Deno's standard library), using the `jsr:` specifier: + +```ts +import path from 'jsr:@std/path@1.0.8' +``` + +## Managing dependencies + +Developing with Edge Functions is similar to developing with Node.js, but with a few key differences. + +In the Deno ecosystem, each function should be treated as an independent project with its own set of dependencies and configurations. This "isolation by design" approach: + +- Ensures each function has explicit control over its dependencies +- Prevents unintended side effects between functions +- Makes deployments more predictable and maintainable +- Allows for different versions of the same dependency across functions + +For these reasons, we recommend maintaining separate configuration files (`deno.json`, `.npmrc`, or `import_map.json`) within each function's directory, even if it means duplicating some configurations. + +There are two ways to manage your dependencies in Supabase Edge Functions: + +### Using deno.json (recommended) + + + +This feature requires Supabase CLI version 1.215.0 or higher. + + + +Each function should have its own `deno.json` file to manage dependencies and configure Deno-specific settings. This ensures proper isolation between functions and is the recommended approach for deployment. For a complete list of supported options, see the [official Deno configuration documentation](https://docs.deno.com/runtime/manual/getting_started/configuration_file). + +```json supabase/functions/my-function/deno.json +{ + "imports": { + "lodash": "https://cdn.skypack.dev/lodash" + } +} +``` + +The recommended file structure for deployment: + +```bash +└── supabase + ├── functions + │ ├── function-one + │ │ ├── index.ts + │ │ ├─- deno.json # Function-specific Deno configuration + │ │ └── .npmrc # Function-specific npm configuration (if needed) + │ └── function-two + │ ├── index.ts + │ ├─- deno.json # Function-specific Deno configuration + │ └── .npmrc # Function-specific npm configuration (if needed) + └── config.toml +``` + + + While it's possible to use a global `deno.json` in the `/supabase/functions` directory for local + development, this approach is not recommended for deployment. Each function should maintain its + own configuration to ensure proper isolation and dependency management. + + +### Using import maps (legacy) + +Import Maps are a legacy way to manage dependencies, similar to a `package.json` file. While still supported, we recommend using `deno.json`. If both exist, `deno.json` takes precedence. + +Each function should have its own `import_map.json` file for proper isolation: + +```json supabase/functions/my-function/import_map.json +{ + "imports": { + "lodash": "https://cdn.skypack.dev/lodash" + } +} +``` + +The recommended file structure: + +```bash +└── supabase + ├── functions + │ ├── function-one + │ │ ├── index.ts + │ │ └── import_map.json # Function-specific import map + │ └── function-two + │ ├── index.ts + │ └── import_map.json # Function-specific import map + └── config.toml +``` + + + While it's possible to use a global `import_map.json` in the `/supabase/functions` directory for + local development, this approach is not recommended for deployment. Each function should maintain + its own import map to ensure proper isolation. + + +If using import maps with VSCode, update your `.vscode/settings.json` to point to your function-specific import map: + +```json settings.json +{ + "deno.enable": true, + "deno.unstable": [ + "bare-node-builtins", + "byonm" + // ... other flags ... + ], + "deno.importMap": "./supabase/functions/my-function/import_map.json" +} +``` + +You can override the default import map location using the `--import-map ` flag with `serve` and `deploy` commands, or by setting the `import_map` property in your `config.toml` file: + +```toml supabase/config.toml +[functions.my-function] +import_map = "./supabase/functions/my-function/import_map.json" +``` + +### Importing from private registries + +This feature requires Supabase CLI version 1.207.9 or higher. + +To use private npm packages, create a `.npmrc` file within your function directory. This ensures proper isolation and dependency management for each function. + +```bash +└── supabase + └── functions + └── my-function + ├── index.ts + ├── deno.json + └── .npmrc # Function-specific npm configuration +``` + +Add your registry details in the `.npmrc` file. Follow [this guide](https://docs.npmjs.com/cli/v10/configuring-npm/npmrc) to learn more about the syntax of npmrc files. + +```plaintext +@myorg:registry=https://npm.registryhost.com +//npm.registryhost.com/:_authToken=VALID_AUTH_TOKEN +``` + + + While it's possible to use a global `.npmrc` in the `/supabase/functions` directory for local + development, we recommend using function-specific `.npmrc` files for deployment to maintain proper + isolation. + + +After configuring your `.npmrc`, you can import the private package in your function code: + +```ts +import MyPackage from 'npm:@myorg/private-package@v1.0.1' + +// use MyPackage +``` + +### Using a custom NPM registry + +This feature requires Supabase CLI version 2.2.8 or higher. + +Some organizations require a custom NPM registry for security and compliance purposes. In such instances, you can specify the custom NPM registry to use via `NPM_CONFIG_REGISTRY` environment variable. + +You can define it in the project's `.env` file or directly specify it when running the deploy command: + +```bash +NPM_CONFIG_REGISTRY=https://custom-registry/ supabase functions deploy my-function +``` + +## Importing types + +If your [environment is set up properly](/docs/guides/functions/local-development) and the module you're importing is exporting types, the import will have types and autocompletion support. + +Some npm packages may not ship out of the box types and you may need to import them from a separate package. You can specify their types with a `@deno-types` directive: + +```ts +// @deno-types="npm:@types/express@^4.17" +import express from 'npm:express@^4.17' +``` + +To include types for built-in Node APIs, add the following line to the top of your imports: + +```ts +/// +``` diff --git a/apps/docs/content/guides/functions/ephemeral-storage.mdx b/apps/docs/content/guides/functions/ephemeral-storage.mdx new file mode 100644 index 00000000000..4d567230977 --- /dev/null +++ b/apps/docs/content/guides/functions/ephemeral-storage.mdx @@ -0,0 +1,54 @@ +--- +id: 'function-ephemeral-storage' +title: 'Ephemeral Storage' +description: 'Read and write from temporary directory' +subtitle: 'Read and write from temporary directory' +--- + +Edge Functions provides ephemeral file storage. You can read and write files to the `/tmp` directory. + +Ephemeral storage will reset on each function invocation. This means the files you write during an invocation can only be read within the same invocation. + +### Use cases + +Here are some use cases where ephemeral storage can be useful: + +- Unzip an archive of CSVs and then add them as records to the DB +- Custom image manipulation workflows (using [`magick-wasm`](https://supabase.com/docs/guides/functions/examples/image-manipulation)) + +You can use [Background Tasks](https://supabase.com/docs/guides/functions/background-tasks) to handle slow file processing outside of a request. + +### How to use + +You can use [Deno File System APIs](https://docs.deno.com/api/deno/file-system) or the [`node:fs` module](https://docs.deno.com/api/node/fs/) to access the `/tmp` path. + +### Example + +Here is an example of how to write a user-uploaded zip file into temporary storage for further processing. + +```js +Deno.serve(async (req) => { + if (req.headers.get('content-type') !== 'application/zip') { + return new Response('file must be a zip file', { + status: 400, + }) + } + + const uploadId = crypto.randomUUID() + await Deno.writeFile('/tmp/' + uploadId, req.body) + + // do something with the written zip file + + return new Response('ok') +}) +``` + +### Unavailable APIs + +Currently, the synchronous APIs (e.g. `Deno.writeFileSync` or `Deno.mkdirSync`) for creating or writing files are not supported. + +You can use sync variations of read APIs (e.g. `Deno.readFileSync`). + +### Limits + +In the hosted platform, a free project can write up to 256MB of data to ephemeral storage. A paid project can write up to 512MB. diff --git a/apps/docs/content/guides/functions/examples/auth-send-email-hook-react-email-resend.mdx b/apps/docs/content/guides/functions/examples/auth-send-email-hook-react-email-resend.mdx index 3eeaded1bed..3ea96f95d88 100644 --- a/apps/docs/content/guides/functions/examples/auth-send-email-hook-react-email-resend.mdx +++ b/apps/docs/content/guides/functions/examples/auth-send-email-hook-react-email-resend.mdx @@ -6,7 +6,7 @@ tocVideo: 'tlA7BomSCgU' Use the [send email hook](/docs/guides/auth/auth-hooks/send-email-hook?queryGroups=language&language=http) to send custom auth emails with [React Email](https://react.email/) and [Resend](https://resend.com/) in Supabase Edge Functions. - + Prefer to jump straight to the code? [Check out the example on GitHub](https://github.com/supabase/supabase/tree/master/examples/edge-functions/supabase/functions/auth-hook-react-email-resend). @@ -114,7 +114,7 @@ Deno.serve(async (req) => { }) ``` -### 3. Create React Email Templates +### 3. Create React Email templates Create a new folder `_templates` and create a new file `magic-link.tsx` with the following code: @@ -251,9 +251,9 @@ const code = { } ``` - + -You can find a selection of React Email templates in the [React Email Eamples](https://react.email/examples). +You can find a selection of React Email templates in the [React Email Examples](https://react.email/examples). @@ -282,7 +282,7 @@ RESEND_API_KEY=your_resend_api_key SEND_EMAIL_HOOK_SECRET= ``` - + You can generate the secret in the [Auth Hooks](/dashboard/project/_/auth/hooks) section of the Supabase dashboard. Make sure to remove the `v1,whsec_` prefix! @@ -296,7 +296,7 @@ supabase secrets set --env-file supabase/functions/.env That's it, now your Supabase Edge Function will be triggered anytime an Auth Email needs to be send to the user! -## More Resources +## More resources - [Send Email Hooks](/docs/guides/auth/auth-hooks/send-email-hook) - [Auth Hooks](/docs/guides/auth/auth-hooks) diff --git a/apps/docs/content/guides/functions/examples/discord-bot.mdx b/apps/docs/content/guides/functions/examples/discord-bot.mdx index bfe4a4e5114..48b0481bbc3 100644 --- a/apps/docs/content/guides/functions/examples/discord-bot.mdx +++ b/apps/docs/content/guides/functions/examples/discord-bot.mdx @@ -138,7 +138,7 @@ function hexToUint8Array(hex: string) { } ``` -## Deploy the Slash Command handler +## Deploy the slash command handler ```bash supabase functions deploy discord-bot --no-verify-jwt @@ -154,7 +154,7 @@ Navigate to your Function details in the Supabase Dashboard to get your Endpoint The application is now ready. Let's proceed to the next section to install it. -## Install the Slash Command on your Discord server +## Install the slash command on your Discord server So to use the `hello` Slash Command, we need to install our Greeter application on our Discord server. Here are the steps: diff --git a/apps/docs/content/guides/functions/examples/github-actions.mdx b/apps/docs/content/guides/functions/examples/github-actions.mdx index 3298913f089..5b665d8f4c8 100644 --- a/apps/docs/content/guides/functions/examples/github-actions.mdx +++ b/apps/docs/content/guides/functions/examples/github-actions.mdx @@ -34,7 +34,7 @@ jobs: PROJECT_ID: YOUR_SUPABASE_PROJECT_ID steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: supabase/setup-cli@v1 with: diff --git a/apps/docs/content/guides/functions/examples/image-manipulation.mdx b/apps/docs/content/guides/functions/examples/image-manipulation.mdx index 0c9ac6c99bf..551da2e9d81 100644 --- a/apps/docs/content/guides/functions/examples/image-manipulation.mdx +++ b/apps/docs/content/guides/functions/examples/image-manipulation.mdx @@ -6,9 +6,9 @@ description: 'How to optimize and transform images using Edge Functions.' Supabase Storage has [out-of-the-box support](https://supabase.com/docs/guides/storage/serving/image-transformations?queryGroups=language&language=js) for the most common image transformations and optimizations you need. If you need to do anything custom beyond what Supabase Storage provides, you can use Edge Functions to write custom image manipulation scripts. -In this example, we will use [magick-wasm](https://github.com/dlemstra/magick-wasm) to perform image manipulations. `magick-wasm` is the WebAssembly port of the popular ImageMagick library and supports processing over 100 file formats. +In this example, we will use [`magick-wasm`](https://github.com/dlemstra/magick-wasm) to perform image manipulations. `magick-wasm` is the WebAssembly port of the popular ImageMagick library and supports processing over 100 file formats. - + Edge Functions currently doesn't support image processing libraries such as `Sharp`, which depend on native libraries. Only WASM-based libraries are supported. @@ -69,7 +69,7 @@ supabase functions deploy image-blur ``` - + Hosted Edge Functions have [limits](https://supabase.com/docs/guides/functions/limits) on memory and CPU usage. diff --git a/apps/docs/content/guides/functions/examples/push-notifications.mdx b/apps/docs/content/guides/functions/examples/push-notifications.mdx index b59ad8879a9..cb157e8230f 100644 --- a/apps/docs/content/guides/functions/examples/push-notifications.mdx +++ b/apps/docs/content/guides/functions/examples/push-notifications.mdx @@ -18,14 +18,14 @@ Push notifications are an important part of any mobile app. They allow you to se Find the example code on [GitHub](https://github.com/supabase/supabase/blob/master/examples/user-management/expo-push-notifications/). - ## Supabase Setup + ## Supabase setup - [Create a new Supabase project](https://database.new). - Link your project: `supabase link --project-ref your-supabase-project-ref` - - Start supabase locally: `supabase start` + - Start Supabase locally: `supabase start` - Push up the schema: `supabase db push` (schema is defined in [supabase/migrations](https://github.com/supabase/supabase/blob/master/examples/user-management/expo-push-notifications/supabase/migrations/)) - ## Expo Setup + ## Expo setup To utilize Expo's push notification service, you must configure your app by installing a set of libraries, implementing functions to handle notifications, and setting up credentials for Android and iOS. Follow the official [Expo Push Notifications Setup Guide](https://docs.expo.dev/push-notifications/push-notifications-setup/) to get the credentials for Android and iOS. This project uses [Expo's EAS build](https://docs.expo.dev/build/introduction/) service to simplify this part. @@ -123,10 +123,10 @@ Push notifications are an important part of any mobile app. They allow you to se Firebase Cloud Messaging (FCM) is a push notification service offered by Google that allows you to send push notifications to your users' devices on iOS, Android, and Web. - + This guide will show you how to send push notifications to your app when a new row is inserted into a table using FCM, Supabase Edge Functions, and database web hooks. - ## Supabase Setup + ## Supabase setup We will create two tables. One to store the user's FCM token and a `notifications` table. The edge function will be triggered when a new row is inserted into the `notifications` table and sends a push notification to the user. @@ -258,7 +258,7 @@ Push notifications are an important part of any mobile app. They allow you to se } ``` - ## FCM Setup + ## FCM setup 1. Follow the official [FCM Setup Guide](https://firebase.google.com/docs/cloud-messaging) to set up FCM for your client side application. 1. Generate a new service account private key from the Firebase console `Project Settings > Service Accounts > Generate new private key`. diff --git a/apps/docs/content/guides/functions/examples/screenshots.mdx b/apps/docs/content/guides/functions/examples/screenshots.mdx index 6fb9f4e0d2a..2e346f9ec9e 100644 --- a/apps/docs/content/guides/functions/examples/screenshots.mdx +++ b/apps/docs/content/guides/functions/examples/screenshots.mdx @@ -12,6 +12,6 @@ description: 'Take screenshots in Edge Functions with Puppeteer and Browserless. >
      -[Puppeteer](https://pptr.dev/) is a handy tool to programmatically take screenshots and generate PDFs. However, trying to do so in Edge Functions can be challenging due to the size restrictions. Luckily there is a [serverless browser offering available](https://www.browserless.io/) that we can connect to via websockets. +[Puppeteer](https://pptr.dev/) is a handy tool to programmatically take screenshots and generate PDFs. However, trying to do so in Edge Functions can be challenging due to the size restrictions. Luckily there is a [serverless browser offering available](https://www.browserless.io/) that we can connect to via WebSockets. Find the code on [GitHub](https://github.com/supabase/supabase/tree/master/examples/edge-functions/supabase/functions/puppeteer). diff --git a/apps/docs/content/guides/functions/examples/semantic-search.mdx b/apps/docs/content/guides/functions/examples/semantic-search.mdx index b42077dd395..21e88052359 100644 --- a/apps/docs/content/guides/functions/examples/semantic-search.mdx +++ b/apps/docs/content/guides/functions/examples/semantic-search.mdx @@ -13,7 +13,7 @@ Since Supabase Edge Runtime [v1.36.0](https://github.com/supabase/edge-runtime/r In this tutorial you're implementing three parts: 1. A [`generate-embedding`](https://github.com/supabase/supabase/tree/master/examples/ai/edge-functions/supabase/functions/generate-embedding/index.ts) database webhook edge function which generates embeddings when a content row is added (or updated) in the [`public.embeddings`](https://github.com/supabase/supabase/tree/master/examples/ai/edge-functions/supabase/migrations/20240408072601_embeddings.sql) table. -2. A [`query_embeddings` Postgres function](https://github.com/supabase/supabase/tree/master/examples/ai/edge-functions/supabase/migrations/20240410031515_vector-search.sql) which allows us to perform similarity search from an egde function via [Remote Procedure Call (RPC)](https://supabase.com/docs/guides/database/functions?language=js). +2. A [`query_embeddings` Postgres function](https://github.com/supabase/supabase/tree/master/examples/ai/edge-functions/supabase/migrations/20240410031515_vector-search.sql) which allows us to perform similarity search from an Edge Function via [Remote Procedure Call (RPC)](https://supabase.com/docs/guides/database/functions?language=js). 3. A [`search` edge function](https://github.com/supabase/supabase/tree/master/examples/ai/edge-functions/supabase/functions/search/index.ts) which generates the embedding for the search term, performs the similarity search via RPC function call, and returns the result. You can find the complete example code on [GitHub](https://github.com/supabase/supabase/tree/master/examples/ai/edge-functions) @@ -61,7 +61,7 @@ Deno.serve(async (req) => { }) ``` -## Create a Postgres Function and RPC +## Create a Database Function and RPC With the embeddings now stored in your Postgres database table, you can query them from Supabase Edge Functions by utilizing [Remote Procedure Calls (RPC)](/docs/guides/database/functions?language=js). diff --git a/apps/docs/content/guides/functions/examples/send-emails.mdx b/apps/docs/content/guides/functions/examples/send-emails.mdx index 4a28a8285ef..8dc64ae0683 100644 --- a/apps/docs/content/guides/functions/examples/send-emails.mdx +++ b/apps/docs/content/guides/functions/examples/send-emails.mdx @@ -77,7 +77,7 @@ Deploy function to Supabase: supabase functions deploy resend --no-verify-jwt ``` - + When you deploy to Supabase, make sure that your `RESEND_API_KEY` is set in [Edge Function Secrets Management](https://supabase.com/dashboard/project/_/settings/functions) diff --git a/apps/docs/content/guides/functions/examples/sentry-monitoring.mdx b/apps/docs/content/guides/functions/examples/sentry-monitoring.mdx index ec522e0a892..29bf2516193 100644 --- a/apps/docs/content/guides/functions/examples/sentry-monitoring.mdx +++ b/apps/docs/content/guides/functions/examples/sentry-monitoring.mdx @@ -79,7 +79,7 @@ supabase functions deploy sentryfied --no-verify-jwt Find the complete example on [GitHub](https://github.com/supabase/supabase/tree/master/examples/edge-functions/supabase/functions/sentryfied/index.ts). -## Working With Scopes +## Working with scopes Sentry Deno SDK currently do not support `Deno.serve` instrumentation, which means that there is no scope separation between requests. Because of that, when the Edge Functions runtime is reused between multiple requests, all globally captured breadcrumbs and contextual data diff --git a/apps/docs/content/guides/functions/import-maps.mdx b/apps/docs/content/guides/functions/import-maps.mdx deleted file mode 100644 index 72d25194ff9..00000000000 --- a/apps/docs/content/guides/functions/import-maps.mdx +++ /dev/null @@ -1,114 +0,0 @@ ---- -id: 'functions-import-maps' -title: 'Managing dependencies' -description: 'Managing packages and dependencies.' -subtitle: 'Managing packages and dependencies.' -tocVideo: 'ILr3cneZuFk' ---- - -Developing with Edge Functions is similar to developing with Node.js, but with a few key differences. This guide will help you understand how to manage your dependencies. - -## Importing dependencies - -Supabase Edge Functions support: - -- The Deno [standard library](https://deno.land/std) -- JavaScript modules from npm (https://docs.deno.com/examples/npm/) -- Built-in [Node APIs](https://docs.deno.com/runtime/manual/node/compatibility) -- Third party modules published to [JSR](https://jsr.io/) or [deno.land/x](https://deno.land/x) - -You can import npm modules using the `npm:` specifier or via CDNs like [esm.sh](https://esm.sh). For example, to import the `supabase-js` package: - -```ts -import { createClient } from 'npm:@supabase/supabase-js@2' -``` - -If you are migrating an existing Node app and if it uses a built-in Node APIs, you can import them to Deno using `node:` specifiers. For example, if your code uses `process.env`, you can import it as follows: - -```ts -import process from 'node:process' -``` - -You can learn more about `npm:` specifiers and Node built-in APIs, in [Deno's documentation](https://docs.deno.com/runtime/manual/node/npm_specifiers). - -## Importing types - -If your [environment is set up properly](/docs/guides/functions/local-development) and the module you're importing is exporting types, the import will have types and autocompletion support. - -Some npm packages may not ship out of the box types and you may need to import them from a separate package. You can specify their types with a `@deno-types` directive: - -```ts -// @deno-types="npm:@types/express@^4.17" -import express from 'npm:express@^4.17' -``` - -To include types for built-in Node APIs, add the following line to the top of your imports: - -```ts -/// -``` - -## Using import maps - -An [Import Map](https://github.com/WICG/import-maps#the-import-map) is similar to a `package.json` file. They are a way to manage your dependencies. Consider this code: - -```ts -// without import maps: -import lodash from 'https://cdn.skypack.dev/lodash' - -// with import maps: -import lodash from 'lodash' -``` - -You can accomplish this using an Import Map, which is a JSON file with the following: - -```json supabase/functions/import_map.json -{ - "imports": { - "lodash": "https://cdn.skypack.dev/lodash" - } -} -``` - -We recommend creating one `import_map.json` within the `/supabase/functions` folder, similar to a `package.json` file, to define imports that can be used across all of your project's functions. - -```bash -└── supabase - ├── functions - │ ├── import_map.json # A top-level import map to use across functions.. - │ ├── function-one - │ │ └── index.ts - └── config.toml -``` - -Alternatively, you can create one `import_map.json` file in each function folder, which will take priority over a top-level file. - -You can override this default behavior by providing the `--import-map ` flag to the `serve` and `deploy` commands. - -### Configuring VSCode - -In order for vscode to understand the imports correctly, you need to specify the `deno.importMap` flag in your `.vscode/settings.json` file: - -```json settings.json -{ - "deno.enable": true, - "deno.unstable": [ - "bare-node-builtins", - "byonm", - "sloppy-imports", - "unsafe-proto", - "webgpu", - "broadcast-channel", - "worker-options", - "cron", - "kv", - "ffi", - "fs", - "http", - "net" - ], - "deno.importMap": "./supabase/functions/import_map.json" -} -``` - -For a full guide on developing with Deno in Visual Studio Code, see [this guide](https://deno.land/manual@v1.25.4/vscode_deno). diff --git a/apps/docs/content/guides/functions/limits.mdx b/apps/docs/content/guides/functions/limits.mdx index fe7c2475d6a..829482dd7ef 100644 --- a/apps/docs/content/guides/functions/limits.mdx +++ b/apps/docs/content/guides/functions/limits.mdx @@ -8,19 +8,19 @@ subtitle: "Limits applied Edge Functions in Supabase's hosted platform." ## Runtime limits - Maximum Memory: 256MB -- Maximum Duration (Wall clock limit): 400s (this is the duration an Edge Function worker will stay active. During this period, a worker can serve multiple requests) -- Maximum CPU Time: 2s -- Request idle timeout: 150s (if an Edge Function doesn't send a response before the timeout, 504 Gateway Timeout will be returned) -- Maximum Function Size (after bundling via CLI): 10MB +- Maximum Duration (Wall clock limit): + This is the duration an Edge Function worker will stay active. During this period, a worker can serve multiple requests or process background tasks. + - Free plan: 150s + - Paid plans: 400s +- Maximum CPU Time: 2s (Amount of actual time spent on the CPU per request - does not include async I/O.) +- Request idle timeout: 150s (If an Edge Function doesn't send a response before the timeout, 504 Gateway Timeout will be returned) +- Maximum Function Size: 20MB (After bundling using CLI) - Maximum log message length: 10,000 characters - Log event threshold: 100 events per 10 seconds -- Max Request: 5GB -- Max Response: No limit ## Other limits & restrictions - Outgoing connections to ports `25` and `587` are not allowed. - Serving of HTML content is only supported with [custom domains](/docs/reference/cli/supabase-domains) (Otherwise `GET` requests that return `text/html` will be rewritten to `text/plain`). -- Deno and Node file system APIs are not available. - Web Worker API (or Node `vm` API) are not available. -- Node Libraries that require multithreading are not supported. Examples: [libvips](https://github.com/libvips/libvips), [sharp](https://github.com/lovell/sharp). +- Node Libraries that require multithreading are not supported. Examples: [`libvips`](https://github.com/libvips/libvips), [sharp](https://github.com/lovell/sharp). diff --git a/apps/docs/content/guides/functions/local-development.mdx b/apps/docs/content/guides/functions/local-development.mdx index 1da210dd2b6..8b8a12f9e96 100644 --- a/apps/docs/content/guides/functions/local-development.mdx +++ b/apps/docs/content/guides/functions/local-development.mdx @@ -13,7 +13,7 @@ You can follow the [Deno guide](https://deno.com/manual@v1.32.5/getting_started/ ## Deno with Visual Studio Code -When using VSCode, you should install both the Deno CLI and the the Deno language server [via this link](vscode:extension/denoland.vscode-deno) or by browsing the extensions in vscode and choosing to install the _Deno_ extension. +When using VSCode, you should install both the Deno CLI and the the Deno language server [via this link](vscode:extension/denoland.vscode-deno) or by browsing the extensions in VSCode and choosing to install the _Deno_ extension. diff --git a/apps/docs/content/guides/functions/logging.mdx b/apps/docs/content/guides/functions/logging.mdx index 4f0e66879cf..62a5cc3cd70 100644 --- a/apps/docs/content/guides/functions/logging.mdx +++ b/apps/docs/content/guides/functions/logging.mdx @@ -61,9 +61,9 @@ Deno.serve(async (req) => { }) ``` -## Logging Tips +## Logging tips -### Logging Request Headers +### Logging request headers When debugging Edge Functions, a common mistake is to try to log headers to the developer console via code like this: @@ -88,7 +88,7 @@ The reason behind this behavior is that [Headers](https://developer.mozilla.org/ However, `Headers` objects are iterable. You can utilize this feature to craft a couple of succinct one-liners for debugging and printing headers. -### Convert headers into an object with Object.fromEntries: +### Convert headers into an object with `Object.fromEntries`: You can use `Object.fromEntries` which is a call to convert the headers into an object: diff --git a/apps/docs/content/guides/functions/pricing.mdx b/apps/docs/content/guides/functions/pricing.mdx new file mode 100644 index 00000000000..90634c298ed --- /dev/null +++ b/apps/docs/content/guides/functions/pricing.mdx @@ -0,0 +1,8 @@ +--- +id: 'functions-pricing' +title: 'Pricing' +--- + + + +For a detailed explanation of how charges are calculated, refer to [Manage Edge Function Invocations usage](/docs/guides/platform/manage-your-usage/edge-function-invocations). diff --git a/apps/docs/content/guides/functions/quickstart.mdx b/apps/docs/content/guides/functions/quickstart.mdx index ac654df9770..a7ef88b6e70 100644 --- a/apps/docs/content/guides/functions/quickstart.mdx +++ b/apps/docs/content/guides/functions/quickstart.mdx @@ -30,7 +30,7 @@ If you're using VS code you can have the CLI automatically create helpful Deno s -If you're using an Intellj IDEA editor such as WebStorm, you can use the `--with-intellij-settings` flag with `supabase init` to create an auto generated deno cofig. +If you're using an IntelliJ IDEA editor such as WebStorm, you can use the `--with-intellij-settings` flag with `supabase init` to create an auto generated Deno config. @@ -186,6 +186,26 @@ verify_jwt = false import_map = './import_map.json' ``` +### Not using TypeScript + +When you create a new Edge Function, it will use TypeScript by default. However, it is possible to write and deploy Edge Functions using pure JavaScript. + +Save your Function as a JavaScript file (e.g. `index.js`) and then update the `supabase/config.toml` as follows: + + + +`entrypoint` is available only in Supabase CLI version 1.215.0 or higher. + + + +```toml supabase/config.toml +[functions.hello-world] +# other entries +entrypoint = './functions/hello-world/index.js' # path must be relative to config.toml +``` + +You can use any `.ts`, `.js`, `.tsx`, `.jsx` or `.mjs` file as the `entrypoint` for a Function. + ### Error handling The `supabase-js` library provides several error types that you can use to handle errors that might occur when invoking Edge Functions: diff --git a/apps/docs/content/guides/functions/routing.mdx b/apps/docs/content/guides/functions/routing.mdx index f13eb21eeed..023e284739f 100644 --- a/apps/docs/content/guides/functions/routing.mdx +++ b/apps/docs/content/guides/functions/routing.mdx @@ -5,8 +5,8 @@ description: 'How to handle custom routing within Edge Functions.' subtitle: 'How to handle custom routing within Edge Functions.' --- -Usually, an Edge Function is written to perform a single action (eg: write a record to the database). However, if your app's logic is split into multiple Edge Functions requests to each action may seem slower. -This is because each Edge Function needs to be booted before serving a request (known as cold starts). If an action is performed less frequently (eg: deleting a record), there is a high-chance of that function experiencing a cold-start. +Usually, an Edge Function is written to perform a single action (e.g. write a record to the database). However, if your app's logic is split into multiple Edge Functions requests to each action may seem slower. +This is because each Edge Function needs to be booted before serving a request (known as cold starts). If an action is performed less frequently (e.g. deleting a record), there is a high-chance of that function experiencing a cold-start. One way to reduce the cold starts and increase performance of your app is to combine multiple actions into a single Edge Function. This way only one instance of the Edge Function needs to be booted and it can handle multiple requests to different actions. For example, we can use a single Edge Function to create a typical CRUD API (create, read, update, delete records). @@ -15,7 +15,7 @@ To combine multiple endpoints into a single Edge Function, you can use web appli Let's dive into some examples. -## Routing with Frameworks +## Routing with frameworks Here's a simple hello world example using some popular web frameworks. @@ -158,12 +158,12 @@ curl --request POST 'https://.supabase.co/functions/v1/hello-world' We should see a response printing `Hello Foo!`. -## Using Route Parameters +## Using route parameters -We can use route parameters to capture values at specific URL segments (eg: `/tasks/:taskId/notes/:noteId`). +We can use route parameters to capture values at specific URL segments (e.g. `/tasks/:taskId/notes/:noteId`). Here's an example Edge Function implemented using the Framework for managing tasks using route parameters. -Keep in mind paths must be prefixed by function name (ie. `tasks` in this example). Route parameters can only be used after the function name prefix. +Keep in mind paths must be prefixed by function name (i.e. `tasks` in this example). Route parameters can only be used after the function name prefix. { -## URL Patterns API +## URL patterns API -If you prefer not to use a web framework, you can directly use [URLPattern API](https://developer.mozilla.org/en-US/docs/Web/API/URL_Pattern_API) within your Edge Functions to implement routing. +If you prefer not to use a web framework, you can directly use [URL Pattern API](https://developer.mozilla.org/en-US/docs/Web/API/URL_Pattern_API) within your Edge Functions to implement routing. This is ideal for small apps with only couple of routes and you want to have a custom matching algorithm. Here is an example Edge Function using URL Patterns API: https://github.com/supabase/supabase/blob/master/examples/edge-functions/supabase/functions/restful-tasks/index.ts diff --git a/apps/docs/content/guides/functions/schedule-functions.mdx b/apps/docs/content/guides/functions/schedule-functions.mdx index 1dd5fb88ed2..213c2c5cd26 100644 --- a/apps/docs/content/guides/functions/schedule-functions.mdx +++ b/apps/docs/content/guides/functions/schedule-functions.mdx @@ -13,14 +13,27 @@ description: 'Schedule Edge Functions with pg_cron.' >
      -The hosted Supabase Platform supports the [`pg_cron` extension](/docs/guides/database/extensions/pgcron), a simple cron-based job scheduler for PostgreSQL that runs inside the database. +The hosted Supabase Platform supports the [`pg_cron` extension](/docs/guides/database/extensions/pgcron), a recurring job scheduler in Postgres. In combination with the [`pg_net` extension](/docs/guides/database/extensions/pgnet), this allows us to invoke Edge Functions periodically on a set schedule. + + +To access the auth token securely for your Edge Function call, we recommend storing them in [Supabase Vault](/docs/guides/database/vault). + + + ## Examples ### Invoke an Edge Function every minute +Store `project_url` and `anon_key` in Supabase Vault: + +```sql +select vault.create_secret('https://project-ref.supabase.co', 'project_url'); +select vault.create_secret('YOUR_SUPABASE_ANON_KEY', 'anon_key'); +``` + Make a POST request to a Supabase Edge Function every minute: ```sql @@ -31,8 +44,11 @@ select $$ select net.http_post( - url:='https://project-ref.supabase.co/functions/v1/function-name', - headers:='{"Content-Type": "application/json", "Authorization": "Bearer YOUR_ANON_KEY"}'::jsonb, + url:= (select decrypted_secret from vault.decrypted_secrets where name = 'project_url') || '/functions/v1/function-name', + headers:=jsonb_build_object( + 'Content-type', 'application/json', + 'Authorization', 'Bearer: ' || (select decrypted_secret from vault.decrypted_secrets where name = 'anon_key') + ), body:=concat('{"time": "', now(), '"}')::jsonb ) as request_id; $$ diff --git a/apps/docs/content/guides/functions/status-codes.mdx b/apps/docs/content/guides/functions/status-codes.mdx index 29f7a4307f6..dfe712f6d3d 100644 --- a/apps/docs/content/guides/functions/status-codes.mdx +++ b/apps/docs/content/guides/functions/status-codes.mdx @@ -5,6 +5,8 @@ description: 'Edge Functions can return following status codes.' subtitle: 'Edge Functions can return following status codes.' --- +{/* supa-mdx-lint-disable Rule001HeadingCase */} + ## 2XX Success A successful Edge Function Response @@ -31,7 +33,7 @@ Edge Functions only support these HTTP methods: 'POST', 'GET', 'PUT', 'PATCH', ' ### 500 Internal Server Error -Edge Function throwed an uncaught exception (`WORKER_ERROR`). Check Edge Function logs to find the cause. +Edge Function threw an uncaught exception (`WORKER_ERROR`). Check Edge Function logs to find the cause. ### 503 Service Unavailable diff --git a/apps/docs/content/guides/functions/troubleshooting.mdx b/apps/docs/content/guides/functions/troubleshooting.mdx index cdeddcbe9c6..2d69c1e0867 100644 --- a/apps/docs/content/guides/functions/troubleshooting.mdx +++ b/apps/docs/content/guides/functions/troubleshooting.mdx @@ -30,6 +30,8 @@ If your Edge Function takes too long to respond or times out: - If the boot times are similar, it’s likely an issue with your function’s code, such as a large dependency, a slow API call, or a complex computation. You can try to optimize your code, reduce the size of your dependencies, or use caching techniques to improve the performance of your function. - If only some of the `booted` events are slow, find the affected `region` in the metadata and submit a support request via the "Help" button at the top. +{/* supa-mdx-lint-disable-next-line Rule001HeadingCase */} + ### Receiving 546 Error Response The 546 error response might occur because: @@ -47,7 +49,7 @@ The 546 error response might occur because: ## Advanced techniques -### Monitoring Edge Function Resource Usage +### Monitoring Edge Function resource usage To determine how much memory and CPU your Edge Function consumes, follow these steps: @@ -62,7 +64,7 @@ To determine how much memory and CPU your Edge Function consumes, follow these s resources. -### Understanding CPU soft and hard Limits +### Understanding CPU soft and hard limits An isolate is like a worker that can handle multiple requests for a function. It works until a time limit of 400 seconds is reached. Now, there are two types of limits for the CPU. @@ -89,7 +91,7 @@ When analyzing dependencies for your Supabase Edge Functions, it's essential to By selectively importing only the required submodules, you can effectively reduce the size of your function's dependencies and optimize its performance. Before finalizing your imports, ensure to review both Deno and NPM dependencies, checking for any unnecessary or redundant dependencies that can be removed. Additionally, check for outdated dependencies and update to the latest versions if possible. -#### Deno Dependencies +#### Deno dependencies Run `deno info`, providing the path to your input map if you use one. Review the dependencies listed in the output. Pay attention to any significantly large dependencies, as they can contribute to increased bundle size and potential boot time issues. @@ -99,7 +101,7 @@ Examine if there are any unnecessary or redundant dependencies that can be remov deno info --import-map=/path/to/import_map.json /path/to/function/index.ts ``` -#### NPM Dependencies +#### NPM dependencies Additionally, if you utilize NPM modules in your Edge Functions, it's crucial to be mindful of their size and impact on the overall bundle size. While importing NPM modules, consider using the notation `import { submodule } from 'npm:package/submodule'` to selectively import specific submodules rather than importing the entire package. This approach can help minimize unnecessary overhead and streamline the execution of your function. diff --git a/apps/docs/content/guides/functions/unit-test.mdx b/apps/docs/content/guides/functions/unit-test.mdx index 5dc3e942fb2..d36ac8f5549 100644 --- a/apps/docs/content/guides/functions/unit-test.mdx +++ b/apps/docs/content/guides/functions/unit-test.mdx @@ -105,7 +105,7 @@ This test case consists of two parts. The first part tests the client library an - 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. - + Please make sure to replace the placeholders (`supabaseUrl`, `supabaseKey`, `my_table`) with the actual values relevant to your Supabase setup. diff --git a/apps/docs/content/guides/functions/wasm.mdx b/apps/docs/content/guides/functions/wasm.mdx new file mode 100644 index 00000000000..b1c26e34de2 --- /dev/null +++ b/apps/docs/content/guides/functions/wasm.mdx @@ -0,0 +1,87 @@ +--- +id: 'function-wasm' +title: 'Using Wasm modules' +description: 'How to use WebAssembly in Edge Functions.' +subtitle: 'How to use WebAssembly in Edge Functions.' +--- + +Edge Functions supports running [WebAssembly (Wasm)](https://developer.mozilla.org/en-US/docs/WebAssembly) modules. WebAssembly is useful if you want to optimize code that's slower to run in JavaScript or require low-level manipulation. + +It also gives you the option to port existing libraries written in other languages to be used with JavaScript. For example, [magick-wasm](https://supabase.com/docs/guides/functions/examples/image-manipulation), which does image manipulation and transforms, is a port of an existing C library to WebAssembly. + +### Writing a Wasm module + +You can use different languages and SDKs to write Wasm modules. For this tutorial, we will write a simple Wasm module in Rust that adds two numbers. + +Follow this [guide on writing Wasm modules in Rust](https://developer.mozilla.org/en-US/docs/WebAssembly/Rust_to_Wasm) to setup your dev environment. + +Create a new Edge Function called `wasm-add`. + +```bash +supabase functions new wasm-add +``` + +Create a new Cargo project for the Wasm module inside the function's directory: + +```bash +cd supabase/functions/wasm-add +cargo new --lib add-wasm +``` + +Add the following code to `add-wasm/src/lib.rs`. + +<$CodeSample +path="edge-functions/supabase/functions/wasm-modules/add-wasm/src/lib.rs" +lines={[[1, -1]]} +meta="lib.rs" +/> + +Update the `add-wasm/Cargo.toml` to include the `wasm-bindgen` dependency. + +<$CodeSample +path="edge-functions/supabase/functions/wasm-modules/add-wasm/Cargo.toml" +lines={[[1, -1]]} +meta="Cargo.toml" +/> + +After that we can build the package, by running: + +```bash +wasm-pack build --target deno +``` + +This will produce a Wasm binary file inside `add-wasm/pkg` directory. + +### Calling the Wasm module from the Edge Function + +Now let's update the Edge Function to call `add` from the Wasm module. + +<$CodeSample +path="edge-functions/supabase/functions/wasm-modules/index.ts" +lines={[[1, -1]]} +meta="index.ts" +/> + + + Supabase Edge Functions currently use Deno 1.46. From [Deno 2.1, importing Wasm + modules](https://deno.com/blog/v2.1) will require even less boilerplate code. + + +### Bundle and deploy the Edge Function + +Before deploying the Edge Function, we need to ensure it bundles the Wasm module with it. We can do this by defining it in the `static_files` for the function in `superbase/config.toml`. + + + You will need update Supabase CLI to 2.7.0 or higher for the `static_files` support. + + +```toml +[functions.wasm-add] +static_files = [ "./functions/wasm-add/add-wasm/pkg/*.wasm"] +``` + +Deploy the function by running: + +```bash +supabase functions deploy wasm-add +``` diff --git a/apps/docs/content/guides/functions/websockets.mdx b/apps/docs/content/guides/functions/websockets.mdx new file mode 100644 index 00000000000..c1c1c214319 --- /dev/null +++ b/apps/docs/content/guides/functions/websockets.mdx @@ -0,0 +1,236 @@ +--- +id: 'function-WebSockets' +title: 'Handling WebSockets' +description: 'How to handle WebSocket connections in Edge Functions' +subtitle: 'How to handle WebSocket connections in Edge Functions' +--- + +Edge Functions supports hosting WebSocket servers that can facilitate bi-directional communications with browser clients. + +You can also establish outgoing WebSocket client connections to another server from Edge Functions (e.g., [OpenAI Realtime API](https://platform.openai.com/docs/guides/realtime/overview)). You can find an example OpenAI Realtime Relay Server implementation on the [supabase-community GitHub account](https://github.com/supabase-community/openai-realtime-console?tab=readme-ov-file#using-supabase-edge-functions-as-a-relay-server). + +### Writing a WebSocket server + +Here are some basic examples of setting up WebSocket servers using Deno and Node.js APIs. + + + +```ts + Deno.serve(req => { + const upgrade = req.headers.get("upgrade") || ""; + + if (upgrade.toLowerCase() != "WebSocket") { + return new Response("request isn't trying to upgrade to WebSocket.", { status: 400 }); + } + + const { socket, response } = Deno.upgradeWebSocket(req); + + socket.onopen = () => console.log("socket opened"); + socket.onmessage = (e) => { + console.log("socket message:", e.data); + socket.send(new Date().toString()); + }; + + socket.onerror = e => console.log("socket errored:", e.message); + socket.onclose = () => console.log("socket closed"); + + return response; + +}); + +```` + + + +```ts +import { createServer } from "node:http"; +import { WebSocketServer } from "npm:ws"; + +const server = createServer(); +// Since we manually created the HTTP server, +// turn on the noServer mode. +const wss = new WebSocketServer({ noServer: true }); + +wss.on("connection", ws => { + console.log("socket opened"); + ws.on("message", (data /** Buffer */, isBinary /** bool */) => { + if (isBinary) { + console.log("socket message:", data); + } else { + console.log("socket message:", data.toString()); + } + + ws.send(new Date().toString()); + }); + + ws.on("error", err => { + console.log("socket errored:", err.message); + }); + + ws.on("close", () => console.log("socket closed")); +}); + +server.on("upgrade", (req, socket, head) => { + wss.handleUpgrade(req, socket, head, ws => { + wss.emit("connection", ws, req); + }); +}); + +server.listen(8080); +```` + + + + +### Outbound WebSockets + +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). + +<$CodeSample +external={true} +org="supabase-community" +repo="openai-realtime-console" +commit="0f93657a71670704fbf77c48cf54d6c9eb956698" +path="/supabase/functions/relay/index.ts" +meta="supabase/functions/relay/index.ts" +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. + +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. + + + +```ts + import { createClient } from "jsr:@supabase/supabase-js@2"; + +const supabase = createClient( +Deno.env.get("SUPABASE_URL"), +Deno.env.get("SUPABASE_SERVICE_ROLE_KEY"), +); +Deno.serve(req => { +const upgrade = req.headers.get("upgrade") || ""; + + if (upgrade.toLowerCase() != "WebSocket") { + return new Response("request isn't trying to upgrade to WebSocket.", { status: 400 }); + } + +// Please be aware query params may be logged in some logging systems. +const url = new URL(req.url); +const jwt = url.searchParams.get("jwt"); +if (!jwt) { +console.error("Auth token not provided"); +return new Response("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 }); +} +if (!data.user) { +console.error("user is not authenticated"); +return new Response("User is not authenticated", { status: 403 }); +} + + const { socket, response } = Deno.upgradeWebSocket(req); + + socket.onopen = () => console.log("socket opened"); + socket.onmessage = (e) => { + console.log("socket message:", e.data); + socket.send(new Date().toString()); + }; + + socket.onerror = e => console.log("socket errored:", e.message); + socket.onclose = () => console.log("socket closed"); + + return response; + +}); + +```` + + +```ts + import { createClient } from "jsr:@supabase/supabase-js@2"; + +const supabase = createClient( + Deno.env.get("SUPABASE_URL"), + Deno.env.get("SUPABASE_SERVICE_ROLE_KEY"), +); + Deno.serve(req => { + const upgrade = req.headers.get("upgrade") || ""; + + if (upgrade.toLowerCase() != "WebSocket") { + return new Response("request isn't trying to upgrade to WebSocket.", { status: 400 }); + } + + // Sec-WebScoket-Protocol may return multiple protocol values `jwt-TOKEN, value1, value 2` + const customProtocols = (req.headers.get("Sec-WebSocket-Protocol") ?? '').split(',').map(p => p.trim()) + const jwt = customProtocols.find(p => p.startsWith('jwt')).replace('jwt-', '') + if (!jwt) { + console.error("Auth token not provided"); + return new Response("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 }); + } + if (!data.user) { + console.error("user is not authenticated"); + return new Response("User is not authenticated", { status: 403 }); + } + + const { socket, response } = Deno.upgradeWebSocket(req); + + socket.onopen = () => console.log("socket opened"); + socket.onmessage = (e) => { + console.log("socket message:", e.data); + socket.send(new Date().toString()); + }; + + socket.onerror = e => console.log("socket errored:", e.message); + socket.onclose = () => console.log("socket closed"); + + return response; + }); +```` + + + + +### 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). + +### 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. + +To prevent that, you can update the `supabase/config.toml` with the following settings: + +```toml +[edge_runtime] +policy = "per_worker" +``` + +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`. diff --git a/apps/docs/content/guides/getting-started.mdx b/apps/docs/content/guides/getting-started.mdx index bc458a709d7..3e50b8ff57b 100644 --- a/apps/docs/content/guides/getting-started.mdx +++ b/apps/docs/content/guides/getting-started.mdx @@ -111,12 +111,19 @@ hideToc: true hasLightIcon: true, }, { - title: 'NuxtJS', + title: 'Nuxt', href: '/guides/getting-started/quickstarts/nuxtjs', description: - 'Learn how to create a Supabase project, add some sample data to your database, and query the data from a NuxtJS app.', + 'Learn how to create a Supabase project, add some sample data to your database, and query the data from a Nuxt app.', icon: '/docs/img/icons/nuxt-icon', }, + { + title: 'Hono', + href: '/guides/getting-started/quickstarts/hono', + description: + 'Learn how to create a Supabase project, add some sample data to your database, secure it with auth, and query the data from a Hono app.', + icon: '/docs/img/icons/hono-icon', + }, { title: 'RedwoodJS', href: '/guides/getting-started/quickstarts/redwoodjs', diff --git a/apps/docs/content/guides/getting-started/ai-prompts.mdx b/apps/docs/content/guides/getting-started/ai-prompts.mdx new file mode 100644 index 00000000000..706a56033ba --- /dev/null +++ b/apps/docs/content/guides/getting-started/ai-prompts.mdx @@ -0,0 +1,16 @@ +--- +title: AI Prompts +subtitle: Prompts for working with Supabase using AI-powered IDE tools +--- + +We've curated a selection of prompts to help you work with Supabase using your favorite AI-powered IDE tools, such as Cursor or GitHub Copilot. + +## How to use + +Copy the prompt to a file in your repo. + +Use the "include file" feature from your AI tool to include the prompt when chatting with your AI assistant. For example, in Cursor, add them as [project rules](https://docs.cursor.com/context/rules-for-ai#project-rules-recommended), with GitHub Copilot, use `#`, and in Zed, use `/file`. + +## Prompts + + diff --git a/apps/docs/content/guides/getting-started/architecture.mdx b/apps/docs/content/guides/getting-started/architecture.mdx index 531cce08e21..6551b14b58d 100644 --- a/apps/docs/content/guides/getting-started/architecture.mdx +++ b/apps/docs/content/guides/getting-started/architecture.mdx @@ -1,7 +1,7 @@ --- -id: 'architecture' title: 'Architecture' description: 'Supabase design and architecture' +tocVideo: 'T-qAtAKjqwc' --- Supabase is open source. We choose open source tools which are scalable and make them simple to use. @@ -66,7 +66,7 @@ We use this with our [`pg_graphql`](https://github.com/supabase/pg_graphql) exte ### Realtime (API & multiplayer) -A scalable websocket engine for managing user Presence, broadcasting messages, and streaming database changes. +A scalable WebSocket engine for managing user Presence, broadcasting messages, and streaming database changes. - Official Docs: [Supabase Realtime docs](/docs/guides/realtime) - Source code: [github.com/supabase/realtime](https://github.com/supabase/realtime) @@ -80,7 +80,7 @@ An S3-compatible object storage service that stores metadata in Postgres. - Official Docs: [Supabase Storage reference docs](/docs/reference/storage) - Source code: [github.com/supabase/storage-api](https://github.com/supabase/storage-api) - License: [Apache 2.0](https://github.com/supabase/storage-api/blob/master/LICENSE) -- Language: NodeJS / TypeScript +- Language: Node.js / TypeScript ### Deno (Edge Functions) @@ -98,7 +98,7 @@ A RESTful API for managing your Postgres. Fetch tables, add roles, and run queri - Official Docs: [supabase.github.io/postgres-meta](https://supabase.github.io/postgres-meta/) - Source code: [github.com/supabase/postgres-meta](https://github.com/supabase/postgres-meta) - License: [Apache 2.0](https://github.com/supabase/postgres-meta/blob/master/LICENSE) -- Language: NodeJS / TypeScript +- Language: Node.js / TypeScript ### Supavisor diff --git a/apps/docs/content/guides/getting-started/features.mdx b/apps/docs/content/guides/getting-started/features.mdx index 6a982ddec9a..80a3a939245 100644 --- a/apps/docs/content/guides/getting-started/features.mdx +++ b/apps/docs/content/guides/getting-started/features.mdx @@ -16,7 +16,7 @@ Every project is a full Postgres database. [Docs](/docs/guides/database). Store vector embeddings right next to the rest of your data. [Docs](/docs/guides/ai). -### Auto-generated Rest API via PostgREST +### Auto-generated REST API via PostgREST RESTful APIs are auto-generated from your database, without a single line of code. [Docs](/docs/guides/api#rest-api-overview). @@ -62,17 +62,13 @@ Manage Supabase infrastructure via Terraform, an Infrastructure as Code tool. [D Deploy read-only databases across multiple regions, for lower latency and better resource management. [Docs](/docs/guides/platform/read-replicas). -### Fly Postgres - -Launch the Supabase stack on Fly.io edge network. [Docs](/docs/guides/platform/fly-postgres). - -### Log Drains +### Log drains Export Supabase logs at to 3rd party providers and external tooling. [Docs](/docs/guides/platform/log-drains). ## Studio -### Studio Single Sign On +### Studio Single Sign-On Login to the Supabase dashboard via SSO. [Docs](/docs/guides/platform/sso). @@ -82,11 +78,11 @@ Login to the Supabase dashboard via SSO. [Docs](/docs/guides/platform/sso). ### Postgres changes -Receive your database changes through websockets. [Docs](/docs/guides/realtime/postgres-changes). +Receive your database changes through WebSockets. [Docs](/docs/guides/realtime/postgres-changes). ### Broadcast -Send messages between connected users through websockets. [Docs](/docs/guides/realtime/broadcast). +Send messages between connected users through WebSockets. [Docs](/docs/guides/realtime/broadcast). ### Presence @@ -114,11 +110,11 @@ Build passwordless logins via magic links for your application or website. [Docs Control the data each user can access with Postgres Policies. [Docs](/docs/guides/database/postgres/row-level-security). -### Captcha protection +### CAPTCHA protection -Add Captcha to your sign-in, sign-up, and password reset forms. [Docs](/docs/guides/auth/auth-captcha). +Add CAPTCHA to your sign-in, sign-up, and password reset forms. [Docs](/docs/guides/auth/auth-captcha). -### Server-side Auth +### Server-Side Auth Helpers for implementing user authentication in popular server-side languages and frameworks like Next.js, SvelteKit and Remix. [Docs](/docs/guides/auth/server-side). @@ -183,11 +179,11 @@ Unofficial libraries are supported by the community. Supabase Features are in 4 different states - Private Alpha, Public Alpha, Beta and Generally Available. -### Private Alpha +### Private alpha Features are initially launched as a private alpha to gather feedback from the community. To join our early access program, send an email to product-ops@supabase.io. -### Public Alpha +### Public alpha The alpha stage indicates that the API might change in the future, not that the service isn’t stable. Even though the [uptime Service Level Agreement](https://supabase.com/sla) does not cover products in Alpha, we do our best to have the service as stable as possible. @@ -195,7 +191,7 @@ The alpha stage indicates that the API might change in the future, not that the Features in Beta are tested by an external penetration tester for security issues. The API is guaranteed to be stable and there is a strict communication process for breaking changes. -### Generally Available +### Generally available In addition to the Beta requirements, features in GA are covered by the [uptime SLA](https://supabase.com/sla). @@ -215,7 +211,6 @@ In addition to the Beta requirements, features in GA are covered by the [uptime | Platform | Branching | `public alpha` | N/A | | Platform | Terraform Provider | `public alpha` | N/A | | Platform | Read Replicas | `private alpha` | N/A | -| Platform | Fly Postgres | `private alpha` | N/A | | Platform | Log Drains | `public alpha` | ✅ | | Studio | | `GA` | ✅ | | Studio | SSO | `GA` | ✅ | @@ -240,7 +235,7 @@ In addition to the Beta requirements, features in GA are covered by the [uptime | Auth | Passwordless login | `GA` | ✅ | | Auth | SSO with SAML | `GA` | ✅ | | Auth | Authorization via RLS | `GA` | ✅ | -| Auth | Captcha protection | `GA` | ✅ | +| Auth | CAPTCHA protection | `GA` | ✅ | | Auth | Server-side Auth | `beta` | ✅ | | CLI | | `GA` | ✅ Works with self-hosted | | Management API | | `GA` | N/A | diff --git a/apps/docs/content/guides/getting-started/mcp.mdx b/apps/docs/content/guides/getting-started/mcp.mdx new file mode 100644 index 00000000000..49691b960a7 --- /dev/null +++ b/apps/docs/content/guides/getting-started/mcp.mdx @@ -0,0 +1,153 @@ +--- +id: 'ai-tools-mcp' +title: 'Model context protocol (MCP)' +description: 'Connect AI tools to Supabase using MCP' +sidebar_label: 'Model context protocol (MCP)' +--- + +The [Model Context Protocol](https://modelcontextprotocol.io/introduction) (MCP) is a standard for connecting Large Language Models (LLMs) to external services. This guide will walk you through how to connect AI tools to Supabase using MCP. + +There are a number of popular AI tools that support MCP, including: + +- [Cursor](https://www.cursor.com/) +- [Claude desktop](https://claude.ai/download) +- [Cline](https://github.com/cline/cline) (VS Code extension) +- [Windsurf](https://docs.codeium.com/windsurf) (Codium) + +Connecting these tools to Supabase will allow you to query your database and perform other SQL operations using natural language commands. + +## Connect to Supabase using MCP + +We will use the [Postgres MCP server](https://github.com/modelcontextprotocol/servers/tree/main/src/postgres) to connect AI tools to Supabase. + +### Step 1: Find your database connection string + +To get started, you will need to retrieve your database connection string. These will differ depending on whether you are using a local or hosted instance of Supabase. + +#### For a local Supabase instance + +When running a local instance of Supabase via the [CLI](/docs/reference/cli/introduction), you can find your connection string by running: + +```shell +supabase status +``` + +or if you are using `npx`: + +```shell +npx supabase status +``` + +This will output a list of details about your local Supabase instance. Copy the `DB URL` field in the output. + +#### For a hosted Supabase instance + +When running a hosted instance of Supabase, you can find your connection string by: + +1. Navigating to your project's [Connection settings](/dashboard/project/_/settings/database?showConnect=true) +1. Copying the connection string found under **Session pooler**. + +### Step 2: Configure in your AI tool + +All MCP compatible tools can connect to Supabase using the [Postgres MCP server](https://github.com/modelcontextprotocol/servers/tree/main/src/postgres). Pass the following CLI command to your tool: + +```shell +npx -y @modelcontextprotocol/server-postgres +``` + +Replace `` with the connection string you retrieved in Step 1. + + + +This assumes you have Node.js and npx installed. If you don't have Node.js or prefer to connect to the server using Docker, you can follow the instructions in the [Postgres MCP server README](https://github.com/modelcontextprotocol/servers/tree/main/src/postgres#docker). + + + +Below are some ways to connect to the Postgres MCP server using popular AI tools: + +#### Cursor + +1. Open Cursor and navigate to **Cursor Settings**. +1. Under the **Features** tab, tap **+ Add new MCP server** under the **MCP Servers** section. +1. Enter the following details: + + - **Name**: Supabase (or your project name) + - **Type**: command + - **Command**: `npx -y @modelcontextprotocol/server-postgres ` + + (replace `` with your connection string) + +1. You should see a green active status after the server is successfully connected. + +#### Claude desktop + +1. Open Claude desktop and navigate to **Settings**. +1. Under the **Developer** tab, tap **Edit Config** to open the configuration file. +1. Add the following configuration: + + ```json + { + "mcpServers": { + "supabase": { + "command": "npx", + "args": ["-y", "@modelcontextprotocol/server-postgres", ""] + } + } + } + ``` + + Replace `` with your connection string. + +1. Save the configuration file and restart Claude desktop. + +1. From the new chat screen, you should see a hammer (MCP) icon appear with the new MCP server available. + +#### Cline + +1. Open the Cline extension in VS Code and tap the **MCP Servers** icon. +1. Tap **Configure MCP Servers** to open the configuration file. +1. Add the following configuration: + + ```json + { + "mcpServers": { + "supabase": { + "command": "npx", + "args": ["-y", "@modelcontextprotocol/server-postgres", ""] + } + } + } + ``` + + Replace `` with your connection string. + +1. Save the configuration file. Cline should automatically reload the configuration. + +1. You should see a green active status after the server is successfully connected. + +#### Windsurf + +1. Open Windsurf and navigate to the Cascade assistant. +1. Tap on the hammer (MCP) icon, then **Configure** to open the configuration file. +1. Add the following configuration: + + ```json + { + "mcpServers": { + "supabase": { + "command": "npx", + "args": ["-y", "@modelcontextprotocol/server-postgres", ""] + } + } + } + ``` + + Replace `` with your connection string. + +1. Save the configuration file and reload by tapping **Refresh** in the Cascade assistant. + +1. You should see a green active status after the server is successfully connected. + +## Next steps + +You are now connected to Supabase using MCP! You can now interact with your database using natural language commands. Try asking your AI tool to query your database, create a new table, or perform other SQL operations. diff --git a/apps/docs/content/guides/getting-started/quickstarts/flutter.mdx b/apps/docs/content/guides/getting-started/quickstarts/flutter.mdx index c9ca79c5b34..29211b1338e 100644 --- a/apps/docs/content/guides/getting-started/quickstarts/flutter.mdx +++ b/apps/docs/content/guides/getting-started/quickstarts/flutter.mdx @@ -8,7 +8,7 @@ hideToc: true - + @@ -101,7 +101,7 @@ hideToc: true @override Widget build(BuildContext context) { return const MaterialApp( - title: 'Countries', + title: 'Instruments', home: HomePage(), ); } @@ -116,7 +116,7 @@ hideToc: true class _HomePageState extends State { final _future = Supabase.instance.client - .from('countries') + .from('instruments') .select(); @override @@ -128,13 +128,13 @@ hideToc: true if (!snapshot.hasData) { return const Center(child: CircularProgressIndicator()); } - final countries = snapshot.data!; + final instruments = snapshot.data!; return ListView.builder( - itemCount: countries.length, + itemCount: instruments.length, itemBuilder: ((context, index) { - final country = countries[index]; + final instrument = instruments[index]; return ListTile( - title: Text(country['name']), + title: Text(instrument['name']), ); }), ); @@ -155,7 +155,7 @@ hideToc: true Run your app on a platform of your choosing! By default an app should launch in your web browser. Note that `supabase_flutter` is compatible with web, iOS, Android, macOS, and Windows apps. - Running the app on MacOS requires additional configuration to [set the entitlements](https://docs.flutter.dev/development/platform-integration/macos/building#setting-up-entitlements). + Running the app on macOS requires additional configuration to [set the entitlements](https://docs.flutter.dev/development/platform-integration/macos/building#setting-up-entitlements). diff --git a/apps/docs/content/guides/getting-started/quickstarts/hono.mdx b/apps/docs/content/guides/getting-started/quickstarts/hono.mdx new file mode 100644 index 00000000000..be46310e9b3 --- /dev/null +++ b/apps/docs/content/guides/getting-started/quickstarts/hono.mdx @@ -0,0 +1,91 @@ +--- +title: 'Use Supabase with Hono' +subtitle: 'Learn how to create a Supabase project, add some sample data to your database, secure it with auth, and query the data from a Hono app.' +breadcrumb: 'Framework Quickstarts' +hideToc: true +--- + + + + + + + + Bootstrap the Hono example app from the Supabase Samples using the CLI. + + + + + + ```bash Terminal + npx supabase@latest bootstrap hono + ``` + + + + + + + + + The `package.json` file in the project includes the necessary dependencies, including `@supabase/supabase-js` and `@supabase/ssr` to help with server-side auth. + + + + + + ```bash Terminal + npm install + ``` + + + + + + + + + Copy the `.env.example` file to `.env` and update the values with your Supabase project URL and anon key. + + Lastly, [enable anonymous sign-ins](https://supabase.com/dashboard/project/_/settings/auth) in the Auth settings. + + + + + + + + + ```bash Terminal + cp .env.example .env + ``` + + + + + + + + + Start the app, go to http://localhost:5173. + + Learn how [server side auth](/docs/guides/auth/server-side/creating-a-client?queryGroups=framework&framework=hono) works with Hono. + + + + + + ```bash Terminal + npm run dev + ``` + + + + + + +## Next steps + +- Learn how [server side auth](/docs/guides/auth/server-side/creating-a-client?queryGroups=framework&framework=hono) works with Hono. +- [Insert more data](/docs/guides/database/import-data) into your database +- Upload and serve static files using [Storage](/docs/guides/storage) diff --git a/apps/docs/content/guides/getting-started/quickstarts/ios-swiftui.mdx b/apps/docs/content/guides/getting-started/quickstarts/ios-swiftui.mdx index 3563c9b5d29..40b6e910092 100644 --- a/apps/docs/content/guides/getting-started/quickstarts/ios-swiftui.mdx +++ b/apps/docs/content/guides/getting-started/quickstarts/ios-swiftui.mdx @@ -8,7 +8,7 @@ hideToc: true - + @@ -39,7 +39,7 @@ hideToc: true - Create a new `Supabase.swift` file add a new supabase instance using your project URL and public API (anon) key: + Create a new `Supabase.swift` file add a new Supabase instance using your project URL and public API (anon) key: @@ -63,18 +63,18 @@ hideToc: true - + Create a decodable struct to deserialize the data from the database. - Add the following code to a new file named `Country.swift`. + Add the following code to a new file named `Instrument.swift`. ```swift Supabase.swift - struct Country: Decodable, Identifiable { + struct Instrument: Decodable, Identifiable { let id: Int let name: String } @@ -99,20 +99,20 @@ hideToc: true ```swift ContentView.swift struct ContentView: View { - @State var countries: [Country] = [] + @State var instruments: [Instrument] = [] var body: some View { - List(countries) { country in - Text(country.name) + List(instruments) { instrument in + Text(instrument.name) } .overlay { - if countries.isEmpty { + if instruments.isEmpty { ProgressView() } } .task { do { - countries = try await supabase.from("countries").select().execute().value + instruments = try await supabase.from("instruments").select().execute().value } catch { dump(error) } diff --git a/apps/docs/content/guides/getting-started/quickstarts/kotlin.mdx b/apps/docs/content/guides/getting-started/quickstarts/kotlin.mdx index 45998483259..fab273e5c00 100644 --- a/apps/docs/content/guides/getting-started/quickstarts/kotlin.mdx +++ b/apps/docs/content/guides/getting-started/quickstarts/kotlin.mdx @@ -8,7 +8,7 @@ hideToc: true - + @@ -103,7 +103,7 @@ hideToc: true - + Create a serializable data class to represent the data from the database. Add the following below the `createSupabaseClient` function in the `MainActivity.kt` file. @@ -112,7 +112,7 @@ hideToc: true ```kotlin @Serializable - data class Country( + data class Instrument( val id: Int, val name: String, ) @@ -144,7 +144,7 @@ hideToc: true modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background ) { - CountriesList() + InstrumentsList() } } } @@ -152,21 +152,21 @@ hideToc: true } @Composable - fun CountriesList() { - var countries by remember { mutableStateOf>(listOf()) } + fun InstrumentsList() { + var instruments by remember { mutableStateOf>(listOf()) } LaunchedEffect(Unit) { withContext(Dispatchers.IO) { - countries = supabase.from("countries") - .select().decodeList() + instruments = supabase.from("instruments") + .select().decodeList() } } LazyColumn { items( - countries, - key = { country -> country.id }, - ) { country -> + instruments, + key = { instrument -> instrument.id }, + ) { instrument -> Text( - country.name, + instrument.name, modifier = Modifier.padding(8.dp), ) } diff --git a/apps/docs/content/guides/getting-started/quickstarts/laravel.mdx b/apps/docs/content/guides/getting-started/quickstarts/laravel.mdx index 3e0f3fd6875..cf2afe249aa 100644 --- a/apps/docs/content/guides/getting-started/quickstarts/laravel.mdx +++ b/apps/docs/content/guides/getting-started/quickstarts/laravel.mdx @@ -63,7 +63,7 @@ hideToc: true ```bash .env DB_CONNECTION=pgsql - DATABASE_URL=postgres://postgres.xxxx:password@xxxx.pooler.supabase.com:5432/postgres + DB_URL=postgres://postgres.xxxx:password@xxxx.pooler.supabase.com:5432/postgres ``` @@ -73,7 +73,7 @@ hideToc: true - By default Laravel uses the `public` schema. We recommend changing this as supabase exposes the `public` schema as a [data API](/docs/guides/api). + By default Laravel uses the `public` schema. We recommend changing this as Supabase exposes the `public` schema as a [data API](/docs/guides/api). You can change the schema of your Laravel application by modifying the `search_path` variable `app/config/database.php`. @@ -84,13 +84,13 @@ hideToc: true ```php app/config/database.php 'pgsql' => [ 'driver' => 'pgsql', - 'url' => env('DATABASE_URL'), + 'url' => env('DB_URL'), 'host' => env('DB_HOST', '127.0.0.1'), 'port' => env('DB_PORT', '5432'), - 'database' => env('DB_DATABASE', 'forge'), - 'username' => env('DB_USERNAME', 'forge'), + 'database' => env('DB_DATABASE', 'laravel'), + 'username' => env('DB_USERNAME', 'root'), 'password' => env('DB_PASSWORD', ''), - 'charset' => 'utf8', + 'charset' => env('DB_CHARSET', 'utf8'), 'prefix' => '', 'prefix_indexes' => true, 'search_path' => 'laravel', diff --git a/apps/docs/content/guides/getting-started/quickstarts/nextjs.mdx b/apps/docs/content/guides/getting-started/quickstarts/nextjs.mdx index 720ea4966e5..48fcf5ef675 100644 --- a/apps/docs/content/guides/getting-started/quickstarts/nextjs.mdx +++ b/apps/docs/content/guides/getting-started/quickstarts/nextjs.mdx @@ -8,54 +8,8 @@ hideToc: true - - - Go to [database.new](https://database.new) and create a new Supabase project. - - When your project is up and running, go to the [Table Editor](https://supabase.com/dashboard/project/_/editor), create a new table and insert some data. - - Alternatively, you can run the following snippet in your project's [SQL Editor](https://supabase.com/dashboard/project/_/sql/new). This will create a `notes` table with some sample data. - - - - - - ```sql SQL_EDITOR - -- Create the table - create table notes ( - id bigint primary key generated always as identity, - title text not null - ); - - -- Insert some sample data into the table - insert into notes (title) - values - ('Today I created a Supabase project.'), - ('I added some data and queried it from Next.js.'), - ('It was awesome!'); - - alter table notes enable row level security; - ``` - - - - - - Make the data in your table publicly readable by adding an RLS policy: - - - - - - ```sql SQL_EDITOR - create policy "public can read notes" - on public.notes - for select to anon - using (true); - ``` - - + @@ -104,9 +58,9 @@ hideToc: true - Create a new file at `app/notes/page.tsx` and populate with the following. + Create a new file at `app/instruments/page.tsx` and populate with the following. - This will select all the rows from the `notes` table in Supabase and render them on the page. + This will select all the rows from the `instruments` table in Supabase and render them on the page. @@ -114,14 +68,14 @@ hideToc: true - ```ts app/notes/page.tsx + ```ts app/instruments/page.tsx import { createClient } from '@/utils/supabase/server'; - export default async function Notes() { - const supabase = createClient(); - const { data: notes } = await supabase.from("notes").select(); + export default async function Instruments() { + const supabase = await createClient(); + const { data: instruments } = await supabase.from("instruments").select(); - return
      {JSON.stringify(notes, null, 2)}
      + return
      {JSON.stringify(instruments, null, 2)}
      } ``` @@ -129,8 +83,8 @@ hideToc: true import { createServerClient } from '@supabase/ssr' import { cookies } from 'next/headers' - export function createClient() { - const cookieStore = cookies() + export async function createClient() { + const cookieStore = await cookies() return createServerClient( process.env.NEXT_PUBLIC_SUPABASE_URL!, @@ -166,7 +120,7 @@ hideToc: true - Run the development server, go to http://localhost:3000/notes in a browser and you should see the list of notes. + Run the development server, go to http://localhost:3000/instruments in a browser and you should see the list of instruments. diff --git a/apps/docs/content/guides/getting-started/quickstarts/nuxtjs.mdx b/apps/docs/content/guides/getting-started/quickstarts/nuxtjs.mdx index cbb726b72fb..6e39dff869d 100644 --- a/apps/docs/content/guides/getting-started/quickstarts/nuxtjs.mdx +++ b/apps/docs/content/guides/getting-started/quickstarts/nuxtjs.mdx @@ -1,6 +1,6 @@ --- -title: 'Use Supabase with NuxtJS' -subtitle: 'Learn how to create a Supabase project, add some sample data to your database, and query the data from a NuxtJS app.' +title: 'Use Supabase with Nuxt' +subtitle: 'Learn how to create a Supabase project, add some sample data to your database, and query the data from a Nuxt app.' breadcrumb: 'Framework Quickstarts' hideToc: true --- @@ -8,16 +8,16 @@ hideToc: true - + - + - Create a Nuxt.js app using the `npx nuxi` command. + Create a Nuxt app using the `npx nuxi` command. @@ -34,9 +34,9 @@ hideToc: true - The fastest way to get started is to use the `supabase-js` client library which provides a convenient interface for working with Supabase from a NuxtJS app. + The fastest way to get started is to use the `supabase-js` client library which provides a convenient interface for working with Supabase from a Nuxt app. - Navigate to the NuxtJS app and install `supabase-js`. + Navigate to the Nuxt app and install `supabase-js`. @@ -53,36 +53,36 @@ hideToc: true - In `src/App.vue`, create a Supabase client using your project URL and public API (anon) key: + In `app.vue`, create a Supabase client using your project URL and public API (anon) key: - Replace the existing content in your `src/App.vue` file with the following code. + Replace the existing content in your `app.vue` file with the following code. - ```vue src/App.vue + ```vue app.vue ``` @@ -94,7 +94,7 @@ hideToc: true - Start the app, navigate to http://localhost:3000 in the browser, open the browser console, and you should see the list of countries. + Start the app, navigate to http://localhost:3000 in the browser, open the browser console, and you should see the list of instruments. @@ -108,3 +108,9 @@ hideToc: true + + + +The community-maintained [@nuxtjs/supabase](https://supabase.nuxtjs.org/) module provides an alternate DX for working with Supabase in Nuxt. + + diff --git a/apps/docs/content/guides/getting-started/quickstarts/reactjs.mdx b/apps/docs/content/guides/getting-started/quickstarts/reactjs.mdx index dfebf4a9fe8..9e430b2786e 100644 --- a/apps/docs/content/guides/getting-started/quickstarts/reactjs.mdx +++ b/apps/docs/content/guides/getting-started/quickstarts/reactjs.mdx @@ -8,7 +8,7 @@ hideToc: true - + @@ -58,7 +58,7 @@ hideToc: true - Add a `getCountries` function to fetch the data and display the query result to the page. + Add a `getInstruments` function to fetch the data and display the query result to the page.
      @@ -71,21 +71,21 @@ hideToc: true const supabase = createClient("https://.supabase.co", ""); function App() { - const [countries, setCountries] = useState([]); + const [instruments, setInstruments] = useState([]); useEffect(() => { - getCountries(); + getInstruments(); }, []); - async function getCountries() { - const { data } = await supabase.from("countries").select(); - setCountries(data); + async function getInstruments() { + const { data } = await supabase.from("instruments").select(); + setInstruments(data); } return (
        - {countries.map((country) => ( -
      • {country.name}
      • + {instruments.map((instrument) => ( +
      • {instrument.name}
      • ))}
      ); @@ -101,7 +101,7 @@ hideToc: true - Start the app, go to http://localhost:5173 in a browser, and open the browser console and you should see the list of countries. + Start the app, go to http://localhost:5173 in a browser, and open the browser console and you should see the list of instruments. diff --git a/apps/docs/content/guides/getting-started/quickstarts/redwoodjs.mdx b/apps/docs/content/guides/getting-started/quickstarts/redwoodjs.mdx index be4ca73f67e..5bae8cd6a65 100644 --- a/apps/docs/content/guides/getting-started/quickstarts/redwoodjs.mdx +++ b/apps/docs/content/guides/getting-started/quickstarts/redwoodjs.mdx @@ -11,7 +11,7 @@ hideToc: true [Create a new project](https://supabase.com/dashboard) in the Supabase Dashboard. - + Be sure to make note of the Database Password you used as you will need this later to connect to your database. @@ -116,7 +116,7 @@ hideToc: true - ```prisma api/prisma/schema.prisma + ```prisma api/db/schema.prisma datasource db { provider = "postgresql" url = env("DATABASE_URL") @@ -128,13 +128,13 @@ hideToc: true - - Create the Country model in `api/db/schema.prisma` and then run `yarn rw prisma migrate dev` from your terminal to apply the migration. + + Create the Instrument model in `api/db/schema.prisma` and then run `yarn rw prisma migrate dev` from your terminal to apply the migration. ```prisma api/db/schema.prisma - model Country { + model Instrument { id Int @id @default(autoincrement()) name String @unique } @@ -145,7 +145,7 @@ hideToc: true - Let's seed the database with a few countries. + Let's seed the database with a few instruments. Update the file `scripts/seeds.ts` to contain the following code: @@ -158,17 +158,17 @@ hideToc: true export default async () => { try { - const data: Prisma.CountryCreateArgs['data'][] = [ - { name: 'United States' }, - { name: 'Canada' }, - { name: 'Mexico' }, + const data: Prisma.InstrumentCreateArgs['data'][] = [ + { name: 'dulcimer' }, + { name: 'harp' }, + { name: 'guitar' }, ] - console.log('Seeding countries ...') + console.log('Seeding instruments ...') - const countries = await db.country.createMany({ data }) + const instruments = await db.instrument.createMany({ data }) - console.log('Done.', countries) + console.log('Done.', instruments) } catch (error) { console.error(error) } @@ -180,7 +180,7 @@ hideToc: true - Run the seed database command to populate the `Country` table with the countries you just created. + Run the seed database command to populate the `Instrument` table with the instruments you just created. @@ -198,13 +198,13 @@ hideToc: true - - Now, we'll use RedwoodJS generators to scaffold a CRUD UI for the `Country` model. + + Now, we'll use RedwoodJS generators to scaffold a CRUD UI for the `Instrument` model. ```bash Terminal - yarn rw g scaffold country + yarn rw g scaffold instrument ``` @@ -223,15 +223,11 @@ hideToc: true - - Click on `/countries` to visit http://localhost:8910/countries where should see the list of countries. + + Click on `/instruments` to visit http://localhost:8910/instruments where should see the list of instruments. - You may now edit, delete, and add new countries using the scaffolded UI. + You may now edit, delete, and add new books using the scaffolded UI. - - ![RedwoodJS Splash Page](/docs/img/redwoodjs-qs-countries-ui.png) - -
      diff --git a/apps/docs/content/guides/getting-started/quickstarts/refine.mdx b/apps/docs/content/guides/getting-started/quickstarts/refine.mdx index b039f19a5cb..f85b656d936 100644 --- a/apps/docs/content/guides/getting-started/quickstarts/refine.mdx +++ b/apps/docs/content/guides/getting-started/quickstarts/refine.mdx @@ -8,7 +8,7 @@ hideToc: true - + @@ -105,13 +105,13 @@ hideToc: true
      - + - You have to then configure resources and define pages for `countries` resource. + You have to then configure resources and define pages for `instruments` resource. - Use the following command to automatically add resources and generate code for pages for `countries` using refine Inferencer. + Use the following command to automatically add resources and generate code for pages for `instruments` using refine Inferencer. - This defines pages for `list`, `create`, `show` and `edit` actions inside the `src/pages/countries/` directory with `` component. + This defines pages for `list`, `create`, `show` and `edit` actions inside the `src/pages/instruments/` directory with `` component. The `` component depends on `@refinedev/react-table` and `@refinedev/react-hook-form` packages. In order to avoid errors, you should install them as dependencies with `npm install @refinedev/react-table @refinedev/react-hook-form`. @@ -129,18 +129,18 @@ hideToc: true ```bash Terminal - npm run refine create-resource countries + npm run refine create-resource instruments ``` - + Add routes for the `list`, `create`, `show`, and `edit` pages. - + You should remove the `index` route for the Welcome page presented with the `` component. @@ -163,7 +163,7 @@ hideToc: true import "./App.css"; import authProvider from "./authProvider"; import { supabaseClient } from "./utility"; - import { CountriesCreate, CountriesEdit, CountriesList, CountriesShow } from "./pages/countries"; + import { InstrumentsCreate, InstrumentsEdit, InstrumentsList, InstrumentsShow } from "./pages/instruments"; function App() { return ( @@ -179,21 +179,21 @@ hideToc: true warnWhenUnsavedChanges: true, }} resources={[{ - name: "countries", - list: "/countries", - create: "/countries/create", - edit: "/countries/edit/:id", - show: "/countries/show/:id" + name: "instruments", + list: "/instruments", + create: "/instruments/create", + edit: "/instruments/edit/:id", + show: "/instruments/show/:id" }]}> } + element={} /> - - } /> - } /> - } /> - } /> + + } /> + } /> + } /> + } /> @@ -212,16 +212,12 @@ hideToc: true - + - Now you should be able to see the countries pages along the `/countries` routes. You may now edit and add new countries using the Inferencer generated UI. + Now you should be able to see the instruments pages along the `/instruments` routes. You may now edit and add new instruments using the Inferencer generated UI. The Inferencer auto-generated code gives you a good starting point on which to keep building your `list`, `create`, `show` and `edit` pages. They can be obtained by clicking the `Show the auto-generated code` buttons in their respective pages. - - ![refine List Page](/docs/img/refine-qs-countries-ui.png) - -
      diff --git a/apps/docs/content/guides/getting-started/quickstarts/solidjs.mdx b/apps/docs/content/guides/getting-started/quickstarts/solidjs.mdx index f5c13f9f771..4e5948912a3 100644 --- a/apps/docs/content/guides/getting-started/quickstarts/solidjs.mdx +++ b/apps/docs/content/guides/getting-started/quickstarts/solidjs.mdx @@ -8,7 +8,7 @@ hideToc: true - + @@ -58,7 +58,7 @@ hideToc: true - Add a `getCountries` function to fetch the data and display the query result to the page. + Add a `getInstruments` function to fetch the data and display the query result to the page. @@ -70,17 +70,17 @@ hideToc: true const supabase = createClient('https://.supabase.co', ''); - async function getCountries() { - const { data } = await supabase.from("countries").select(); + async function getInstruments() { + const { data } = await supabase.from("instruments").select(); return data; } function App() { - const [countries] = createResource(getCountries); + const [instruments] = createResource(getInstruments); return (
        - {(country) =>
      • {country.name}
      • }
        + {(instrument) =>
      • {instrument.name}
      • }
      ); } @@ -95,7 +95,7 @@ hideToc: true - Start the app and go to http://localhost:3000 in a browser and you should see the list of countries. + Start the app and go to http://localhost:3000 in a browser and you should see the list of instruments. diff --git a/apps/docs/content/guides/getting-started/quickstarts/sveltekit.mdx b/apps/docs/content/guides/getting-started/quickstarts/sveltekit.mdx index 833df3d0817..c2d39d35556 100644 --- a/apps/docs/content/guides/getting-started/quickstarts/sveltekit.mdx +++ b/apps/docs/content/guides/getting-started/quickstarts/sveltekit.mdx @@ -8,7 +8,7 @@ hideToc: true - + @@ -24,7 +24,7 @@ hideToc: true ```bash Terminal - npm create svelte@latest myapp + npx sv create my-app ``` @@ -43,7 +43,7 @@ hideToc: true ```bash Terminal - cd myapp && npm install @supabase/supabase-js + cd my-app && npm install @supabase/supabase-js ``` @@ -53,7 +53,7 @@ hideToc: true - Create a `/src/lib` directory in your SvelteKit app, create a file called `supabaseClient.js` and add the following code to initialize the Supabase client with your project URL and public API (anon) key: + Create a `src/lib` directory in your SvelteKit app, create a file called `supabaseClient.js` and add the following code to initialize the Supabase client with your project URL and public API (anon) key: @@ -77,7 +77,7 @@ hideToc: true Use `load` method to fetch the data server-side and display the query results as a simple list. - Create `+page.server.js` file in the `routes` directory with the following code. + Create `+page.server.js` file in the `src/routes` directory with the following code. @@ -87,9 +87,9 @@ hideToc: true import { supabase } from "$lib/supabaseClient"; export async function load() { - const { data } = await supabase.from("countries").select(); + const { data } = await supabase.from("instruments").select(); return { - countries: data ?? [], + instruments: data ?? [], }; } ``` @@ -98,7 +98,7 @@ hideToc: true - Replace the existing content in your `+page.svelte` file in the `routes` directory with the following code. + Replace the existing content in your `+page.svelte` file in the `src/routes` directory with the following code. @@ -106,12 +106,12 @@ hideToc: true ```svelte src/routes/+page.svelte
        - {#each data.countries as country} -
      • {country.name}
      • + {#each data.instruments as instrument} +
      • {instrument.name}
      • {/each}
      ``` @@ -123,7 +123,7 @@ hideToc: true - Start the app and go to http://localhost:5173 in a browser and you should see the list of countries. + Start the app and go to http://localhost:5173 in a browser and you should see the list of instruments. diff --git a/apps/docs/content/guides/getting-started/quickstarts/vue.mdx b/apps/docs/content/guides/getting-started/quickstarts/vue.mdx index 4d7443dd468..fdd447e046c 100644 --- a/apps/docs/content/guides/getting-started/quickstarts/vue.mdx +++ b/apps/docs/content/guides/getting-started/quickstarts/vue.mdx @@ -8,7 +8,7 @@ hideToc: true - + @@ -86,21 +86,21 @@ hideToc: true import { ref, onMounted } from 'vue' import { supabase } from './lib/supabaseClient' - const countries = ref([]) + const instruments = ref([]) - async function getCountries() { - const { data } = await supabase.from('countries').select() - countries.value = data + async function getInstruments() { + const { data } = await supabase.from('instruments').select() + instruments.value = data } onMounted(() => { - getCountries() + getInstruments() }) ``` @@ -112,7 +112,7 @@ hideToc: true - Start the app and go to http://localhost:5173 in a browser and you should see the list of countries. + Start the app and go to http://localhost:5173 in a browser and you should see the list of instruments. diff --git a/apps/docs/content/guides/getting-started/tutorials/with-angular.mdx b/apps/docs/content/guides/getting-started/tutorials/with-angular.mdx index da7aa2b7d1c..1fc7ee77539 100644 --- a/apps/docs/content/guides/getting-started/tutorials/with-angular.mdx +++ b/apps/docs/content/guides/getting-started/tutorials/with-angular.mdx @@ -47,7 +47,7 @@ export const environment = { } ``` -Now that we have the API credentials in place, let's create a **SupabaseService** with `ng g s supabase` to initialize the Supabase client and implement functions to communicate with the Supabase API. +Now that we have the API credentials in place, let's create a `SupabaseService` with `ng g s supabase` to initialize the Supabase client and implement functions to communicate with the Supabase API. ```ts src/app/supabase.service.ts import { Injectable } from '@angular/core' @@ -130,7 +130,7 @@ Optionally, update [src/styles.css](https://raw.githubusercontent.com/supabase/s ### Set up a login component Let's set up an Angular component to manage logins and sign ups. We'll use Magic Links, so users can sign in with their email without using passwords. -Create an **AuthComponent** with `ng g c auth` Angular CLI command. +Create an `AuthComponent` with `ng g c auth` Angular CLI command. ```ts src/app/auth/auth.component.ts import { Component } from '@angular/core' @@ -202,7 +202,7 @@ export class AuthComponent { ### Account page Users also need a way to edit their profile details and manage their accounts after signing in. -Create an **AccountComponent** with the `ng g c account` Angular CLI command. +Create an `AccountComponent` with the `ng g c account` Angular CLI command. ```ts src/app/account/account.component.ts import { Component, Input, OnInit } from '@angular/core' @@ -326,7 +326,7 @@ export class AccountComponent implements OnInit { ### Launch! -Now that we have all the components in place, let's update **AppComponent**: +Now that we have all the components in place, let's update `AppComponent`: ```ts src/app/app.component.ts import { Component, OnInit } from '@angular/core' @@ -397,7 +397,7 @@ Every Supabase project is configured with [Storage](/docs/guides/storage) for ma ### Create an upload widget Let's create an avatar for the user so that they can upload a profile photo. -Create an **AvatarComponent** with `ng g c avatar` Angular CLI command. +Create an `AvatarComponent` with `ng g c avatar` Angular CLI command. ```ts src/app/avatar/avatar.component.ts import { Component, EventEmitter, Input, Output } from '@angular/core' @@ -492,7 +492,7 @@ export class AvatarComponent { ### Add the new widget -And then we can add the widget on top of the **AccountComponent** HTML template: +And then we can add the widget on top of the `AccountComponent` HTML template: ```html src/app/account.component.html
      @@ -501,7 +501,7 @@ And then we can add the widget on top of the **AccountComponent** HTML template:
      ``` -And add an `updateAvatar` function along with an `avatarUrl` getter to the **AccountComponent** typescript file: +And add an `updateAvatar` function along with an `avatarUrl` getter to the `AccountComponent` typescript file: ```ts src/app/account.component.ts @Component({ diff --git a/apps/docs/content/guides/getting-started/tutorials/with-expo-react-native.mdx b/apps/docs/content/guides/getting-started/tutorials/with-expo-react-native.mdx index a86e882cd3c..63a270a0ecb 100644 --- a/apps/docs/content/guides/getting-started/tutorials/with-expo-react-native.mdx +++ b/apps/docs/content/guides/getting-started/tutorials/with-expo-react-native.mdx @@ -40,7 +40,7 @@ npx expo install @supabase/supabase-js @react-native-async-storage/async-storage Now let's create a helper file to initialize the Supabase client. We need the API URL and the `anon` key that you copied [earlier](#get-the-api-keys). These variables are safe to expose in your Expo app since Supabase has -[Row Level Security](/docs/guides/auth#row-level-security) enabled on your Database. +[Row Level Security](/docs/guides/database/postgres/row-level-security) enabled on your Database. Edit the `ios/Runner/Info.plist` file. -Add CFBundleURLTypes to enable deep linking: +Add `CFBundleURLTypes` to enable deep linking: ```xml ios/Runner/Info.plist" diff --git a/apps/docs/content/guides/getting-started/tutorials/with-ionic-angular.mdx b/apps/docs/content/guides/getting-started/tutorials/with-ionic-angular.mdx index b22f2f867d3..09c3b87a039 100644 --- a/apps/docs/content/guides/getting-started/tutorials/with-ionic-angular.mdx +++ b/apps/docs/content/guides/getting-started/tutorials/with-ionic-angular.mdx @@ -48,7 +48,7 @@ export const environment = { } ``` -Now that we have the API credentials in place, let's create a **SupabaseService** with `ionic g s supabase` to initialize the Supabase client and implement functions to communicate with the Supabase API. +Now that we have the API credentials in place, let's create a `SupabaseService` with `ionic g s supabase` to initialize the Supabase client and implement functions to communicate with the Supabase API. ```ts src/app/supabase.service.ts import { Injectable } from '@angular/core' @@ -136,7 +136,7 @@ export class SupabaseService { ### Set up a login route Let's set up a route to manage logins and signups. We'll use Magic Links so users can sign in with their email without using passwords. -Create a **LoginPage** with the `ionic g page login` Ionic CLI command. +Create a `LoginPage` with the `ionic g page login` Ionic CLI command. @@ -204,7 +204,7 @@ export class LoginPage { ### Account page After a user is signed in, we can allow them to edit their profile details and manage their account. -Create an **AccountComponent** with `ionic g page account` Ionic CLI command. +Create an `AccountComponent` with `ionic g page account` Ionic CLI command. ```ts src/app/account.page.ts import { Component, OnInit } from '@angular/core' @@ -310,7 +310,7 @@ export class AccountPage implements OnInit { ### Launch! -Now that we have all the components in place, let's update **AppComponent**: +Now that we have all the components in place, let's update `AppComponent`: ```ts src/app/app.component.ts import { Component } from '@angular/core' @@ -341,7 +341,7 @@ export class AppComponent { } ``` -Then update the **AppRoutingModule** +Then update the `AppRoutingModule` ```ts src/app/app-routing.module.ts" import { NgModule } from '@angular/core' @@ -393,7 +393,7 @@ First, install two packages in order to interact with the user's camera. npm install @ionic/pwa-elements @capacitor/camera ``` -[CapacitorJS](https://capacitorjs.com) is a cross-platform native runtime from Ionic that enables web apps to be deployed through the app store and provides access to native device API. +[Capacitor](https://capacitorjs.com) is a cross-platform native runtime from Ionic that enables web apps to be deployed through the app store and provides access to native device API. Ionic PWA elements is a companion package that will polyfill certain browser APIs that provide no user interface with custom Ionic UI. @@ -417,7 +417,7 @@ platformBrowserDynamic() .catch((err) => console.log(err)) ``` -Then create an **AvatarComponent** with this Ionic CLI command: +Then create an `AvatarComponent` with this Ionic CLI command: ```bash ionic g component avatar --module=/src/app/account/account.module.ts --create-module @@ -536,7 +536,7 @@ export class AvatarComponent { ### Add the new widget -And then, we can add the widget on top of the **AccountComponent** HTML template: +And then, we can add the widget on top of the `AccountComponent` HTML template: ```ts src/app/account.component.ts template: ` diff --git a/apps/docs/content/guides/getting-started/tutorials/with-ionic-react.mdx b/apps/docs/content/guides/getting-started/tutorials/with-ionic-react.mdx index cbfc4db3f49..5ad2106479f 100644 --- a/apps/docs/content/guides/getting-started/tutorials/with-ionic-react.mdx +++ b/apps/docs/content/guides/getting-started/tutorials/with-ionic-react.mdx @@ -363,7 +363,7 @@ First install two packages in order to interact with the user's camera. npm install @ionic/pwa-elements @capacitor/camera ``` -[CapacitorJS](https://capacitorjs.com) is a cross platform native runtime from Ionic that enables web apps to be deployed through the app store and provides access to native device API. +[Capacitor](https://capacitorjs.com) is a cross platform native runtime from Ionic that enables web apps to be deployed through the app store and provides access to native device API. Ionic PWA elements is a companion package that will polyfill certain browser APIs that provide no user interface with custom Ionic UI. @@ -390,7 +390,7 @@ serviceWorkerRegistration.unregister() reportWebVitals() ``` -Then create an **AvatarComponent**. +Then create an `AvatarComponent`. ```jsx src/components/Avatar.tsx import { IonIcon } from '@ionic/react'; diff --git a/apps/docs/content/guides/getting-started/tutorials/with-ionic-vue.mdx b/apps/docs/content/guides/getting-started/tutorials/with-ionic-vue.mdx index 00b85b6aa6f..58388b7796c 100644 --- a/apps/docs/content/guides/getting-started/tutorials/with-ionic-vue.mdx +++ b/apps/docs/content/guides/getting-started/tutorials/with-ionic-vue.mdx @@ -396,7 +396,7 @@ First install two packages in order to interact with the user's camera. npm install @ionic/pwa-elements @capacitor/camera ``` -[CapacitorJS](https://capacitorjs.com) is a cross platform native runtime from Ionic that enables web apps to be deployed through the app store and provides access to native device API. +[Capacitor](https://capacitorjs.com) is a cross-platform native runtime from Ionic that enables web apps to be deployed through the app store and provides access to native device API. Ionic PWA elements is a companion package that will polyfill certain browser APIs that provide no user interface with custom Ionic UI. @@ -423,7 +423,7 @@ router.isReady().then(() => { }) ``` -Then create an **AvatarComponent**. +Then create an `AvatarComponent`. ```html src/components/Avatar.vue