From d6b4ab1a60b8d8fa4e75e492c9c5ee84ac1ff0f8 Mon Sep 17 00:00:00 2001 From: Stojan Dimitrovski Date: Mon, 14 Jul 2025 09:05:05 +0200 Subject: [PATCH 01/93] feat: bump auth-js to v2.71.0-rc.7 (#36978) * feat: bump auth-js to v2.71.0-rc.7 * Use the version specified in the catalog. * bump to rc.8 --------- Co-authored-by: Ivan Vasilov --- package.json | 2 +- pnpm-lock.yaml | 33 +++++++++++++++++---------------- pnpm-workspace.yaml | 2 +- 3 files changed, 19 insertions(+), 18 deletions(-) diff --git a/package.json b/package.json index 6a8001f58d1..abe0d27ac45 100644 --- a/package.json +++ b/package.json @@ -66,7 +66,7 @@ }, "pnpm": { "overrides": { - "@supabase/supabase-js>@supabase/auth-js": "2.71.0-rc.4", + "@supabase/supabase-js>@supabase/auth-js": "catalog:", "@tanstack/directive-functions-plugin>vite": "catalog:", "@tanstack/react-start-plugin>vite": "catalog:", "esbuild": "^0.25.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c6eb00d36ad..77d9e63c5ed 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -7,8 +7,8 @@ settings: catalogs: default: '@supabase/auth-js': - specifier: 2.71.0-rc.4 - version: 2.71.0-rc.4 + specifier: 2.71.0-rc.8 + version: 2.71.0-rc.8 '@supabase/realtime-js': specifier: ^2.11.3 version: 2.11.3 @@ -41,7 +41,7 @@ catalogs: version: 6.3.5 overrides: - '@supabase/supabase-js>@supabase/auth-js': 2.71.0-rc.4 + '@supabase/supabase-js>@supabase/auth-js': 2.71.0-rc.8 '@tanstack/directive-functions-plugin>vite': ^6.2.7 '@tanstack/react-start-plugin>vite': ^6.2.7 esbuild: ^0.25.2 @@ -331,7 +331,7 @@ importers: version: 1.0.3(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@sentry/nextjs': specifier: ^9.15.0 - version: 9.15.0(@opentelemetry/context-async-hooks@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.57.2(@opentelemetry/api@1.9.0)(supports-color@8.1.1))(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.0))(encoding@0.1.13)(next@15.3.1(@opentelemetry/api@1.9.0)(@playwright/test@1.53.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(react@18.3.1)(supports-color@8.1.1)(webpack@5.94.0) + version: 9.15.0(@opentelemetry/context-async-hooks@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.57.2(@opentelemetry/api@1.9.0)(supports-color@8.1.1))(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.0))(encoding@0.1.13)(next@15.3.1(@babel/core@7.26.10(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.53.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(react@18.3.1)(supports-color@8.1.1)(webpack@5.94.0) '@supabase/supabase-js': specifier: 'catalog:' version: 2.49.3 @@ -451,7 +451,7 @@ importers: version: 0.3.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) nuqs: specifier: ^1.19.1 - version: 1.19.1(next@15.3.1(@opentelemetry/api@1.9.0)(@playwright/test@1.53.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4)) + version: 1.19.1(next@15.3.1(@babel/core@7.26.10(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.53.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4)) openai: specifier: ^4.20.1 version: 4.71.1(encoding@0.1.13)(zod@3.23.8) @@ -737,7 +737,7 @@ importers: version: 5.5.0 '@supabase/auth-js': specifier: 'catalog:' - version: 2.71.0-rc.4 + version: 2.71.0-rc.8 '@supabase/mcp-server-supabase': specifier: ^0.4.4 version: 0.4.4(supports-color@8.1.1) @@ -1137,7 +1137,7 @@ importers: version: 2.4.11(typescript@5.5.2) next-router-mock: specifier: ^0.9.13 - version: 0.9.13(next@15.3.1(@opentelemetry/api@1.9.0)(@playwright/test@1.53.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(react@18.3.1) + version: 0.9.13(next@15.3.1(@babel/core@7.26.10(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.53.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(react@18.3.1) postcss: specifier: ^8.5.3 version: 8.5.3 @@ -1710,7 +1710,7 @@ importers: dependencies: '@supabase/auth-js': specifier: 'catalog:' - version: 2.71.0-rc.4 + version: 2.71.0-rc.8 '@supabase/supabase-js': specifier: 'catalog:' version: 2.49.3 @@ -2293,7 +2293,7 @@ importers: version: link:../api-types next-router-mock: specifier: ^0.9.13 - version: 0.9.13(next@15.3.1(@opentelemetry/api@1.9.0)(@playwright/test@1.53.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(react@18.3.1) + version: 0.9.13(next@15.3.1(@babel/core@7.26.10(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.53.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(react@18.3.1) tsx: specifier: ^4.19.3 version: 4.19.3 @@ -7865,8 +7865,8 @@ packages: resolution: {integrity: sha512-lkfjyAd34aeMpTKKcEVfy8IUyEsjuAT3t9EXr5yZDtdIUncnZpedl/xLV16Dkd4z+fQwixScsCCDxSMNtBOgpQ==} engines: {node: '>=12.16'} - '@supabase/auth-js@2.71.0-rc.4': - resolution: {integrity: sha512-K+R8WcRnOW8w6ku2lzTlmlk5FdGNEoVSrAiTl2dsvJJ30ceaqlCHF+ZNPHXzMy9t77hYdY322+2Ex/osXkkS5w==} + '@supabase/auth-js@2.71.0-rc.8': + resolution: {integrity: sha512-GX4UDxkhzk9Es8dGSJIcxVrgOPm9pUoEubNavShylpcCfDm5DELzTsc09GRUFaItfmnh5rWXDhV97PfLz1J6zA==} '@supabase/functions-js@2.4.4': resolution: {integrity: sha512-WL2p6r4AXNGwop7iwvul2BvOtuJ1YQy8EbOd0dhG1oN1q8el/BIRSFCFnWAMM/vJJlHWLi4ad22sKbKr9mvjoA==} @@ -11111,6 +11111,7 @@ packages: resolution: {integrity: sha512-t0q23FIpvHDTtnORW+bDJziGsal5uh9RJTJ1fyH8drd4lICOoXhJ5pLMUZ5C0VQei6dNmwTzzoTRgMkO9JgHEQ==} peerDependencies: eslint: '>= 5' + bundledDependencies: [] eslint-plugin-import@2.31.0: resolution: {integrity: sha512-ixmkI62Rbc2/w8Vfxyh1jQRTdRTF52VxwRVHl/ykPAmqG+Nb7/kNn+byLP0LxPgI7zWA16Jt82SybJInmMia3A==} @@ -25768,7 +25769,7 @@ snapshots: - supports-color - webpack - '@sentry/nextjs@9.15.0(@opentelemetry/context-async-hooks@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.57.2(@opentelemetry/api@1.9.0)(supports-color@8.1.1))(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.0))(encoding@0.1.13)(next@15.3.1(@opentelemetry/api@1.9.0)(@playwright/test@1.53.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(react@18.3.1)(supports-color@8.1.1)(webpack@5.94.0)': + '@sentry/nextjs@9.15.0(@opentelemetry/context-async-hooks@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.57.2(@opentelemetry/api@1.9.0)(supports-color@8.1.1))(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.0))(encoding@0.1.13)(next@15.3.1(@babel/core@7.26.10(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.53.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(react@18.3.1)(supports-color@8.1.1)(webpack@5.94.0)': dependencies: '@opentelemetry/api': 1.9.0 '@opentelemetry/semantic-conventions': 1.32.0 @@ -26580,7 +26581,7 @@ snapshots: '@stripe/stripe-js@5.5.0': {} - '@supabase/auth-js@2.71.0-rc.4': + '@supabase/auth-js@2.71.0-rc.8': dependencies: '@supabase/node-fetch': 2.6.15 @@ -26737,7 +26738,7 @@ snapshots: '@supabase/supabase-js@2.49.3': dependencies: - '@supabase/auth-js': 2.71.0-rc.4 + '@supabase/auth-js': 2.71.0-rc.8 '@supabase/functions-js': 2.4.4 '@supabase/node-fetch': 2.6.15 '@supabase/postgrest-js': 1.19.2 @@ -34702,7 +34703,7 @@ snapshots: dependencies: js-yaml-loader: 1.2.2 - next-router-mock@0.9.13(next@15.3.1(@opentelemetry/api@1.9.0)(@playwright/test@1.53.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(react@18.3.1): + next-router-mock@0.9.13(next@15.3.1(@babel/core@7.26.10(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.53.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(react@18.3.1): dependencies: next: 15.3.1(@babel/core@7.26.10(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.53.0)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4) react: 18.3.1 @@ -35039,7 +35040,7 @@ snapshots: number-flow@0.3.7: {} - nuqs@1.19.1(next@15.3.1(@opentelemetry/api@1.9.0)(@playwright/test@1.53.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4)): + nuqs@1.19.1(next@15.3.1(@babel/core@7.26.10(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.53.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4)): dependencies: mitt: 3.0.1 next: 15.3.1(@babel/core@7.26.10(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.53.0)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4) diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index bc610cd0fe9..f3018fea5ab 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -5,7 +5,7 @@ packages: catalog: '@types/node': ^22.0.0 - '@supabase/auth-js': 2.71.0-rc.4 + '@supabase/auth-js': 2.71.0-rc.8 '@supabase/supabase-js': ^2.47.14 '@supabase/realtime-js': ^2.11.3 next: 15.3.1 From d8f34dcd79f21274a1d43f0888209cf58626424e Mon Sep 17 00:00:00 2001 From: Saxon Fletcher Date: Mon, 14 Jul 2025 17:33:39 +1000 Subject: [PATCH 02/93] remove duplicate download (#37093) --- .../interfaces/Database/Schemas/SchemaGraph.tsx | 8 -------- 1 file changed, 8 deletions(-) diff --git a/apps/studio/components/interfaces/Database/Schemas/SchemaGraph.tsx b/apps/studio/components/interfaces/Database/Schemas/SchemaGraph.tsx index 6be17b8c501..132b4523d61 100644 --- a/apps/studio/components/interfaces/Database/Schemas/SchemaGraph.tsx +++ b/apps/studio/components/interfaces/Database/Schemas/SchemaGraph.tsx @@ -226,14 +226,6 @@ export const SchemaGraph = () => { > Copy as SQL - } - onClick={() => downloadImage('png')} - tooltip={{ content: { side: 'bottom', text: 'Download current view as PNG' } }} - /> Date: Mon, 14 Jul 2025 17:41:06 +1000 Subject: [PATCH 03/93] Branches page (#37080) * move branches to page layout * changes * more clean up * use branch status * clean up * gitless flag * tidy up * revert generate * Couple of fixes * Refactor some stuff, standardize copy, add missing loading state * Smol refactor * Replace ephemeral with preview for more consistant language * Few more fixes * Refactor Overview.tsx to use DropdownMenuItemTooltip component * Final round of refactors after code review --------- Co-authored-by: Joshen Lim --- .../BranchManagement/BranchManagement.tsx | 440 ------------------ .../BranchManagement/BranchPanels.tsx | 406 ++-------------- .../BranchManagement/BranchSelector.tsx | 28 +- .../BranchManagement/CreateBranchModal.tsx | 6 +- .../EdgeFunctionsDiffPanel.tsx | 4 +- .../BranchManagement/EditBranchModal.tsx | 2 +- .../BranchManagement/EmptyStates.tsx | 150 ++---- .../interfaces/BranchManagement/Overview.tsx | 382 ++++++++++++++- .../BranchManagement/WorkflowLogs.tsx | 54 ++- .../GithubIntegration/GitHubStatus.tsx | 123 ++--- .../layouts/AppLayout/BranchDropdown.tsx | 235 ++++------ .../layouts/BranchLayout/BranchLayout.tsx | 40 ++ .../BranchLayout/BranchLayout.utils.ts | 23 + .../layouts/ProjectLayout/ProjectLayout.tsx | 8 +- apps/studio/pages/project/[ref]/branches.tsx | 16 - .../pages/project/[ref]/branches/index.tsx | 232 +++++++++ .../project/[ref]/branches/merge-requests.tsx | 364 +++++++++++++++ apps/studio/pages/project/[ref]/merge.tsx | 171 +++---- 18 files changed, 1414 insertions(+), 1270 deletions(-) delete mode 100644 apps/studio/components/interfaces/BranchManagement/BranchManagement.tsx create mode 100644 apps/studio/components/layouts/BranchLayout/BranchLayout.tsx create mode 100644 apps/studio/components/layouts/BranchLayout/BranchLayout.utils.ts delete mode 100644 apps/studio/pages/project/[ref]/branches.tsx create mode 100644 apps/studio/pages/project/[ref]/branches/index.tsx create mode 100644 apps/studio/pages/project/[ref]/branches/merge-requests.tsx diff --git a/apps/studio/components/interfaces/BranchManagement/BranchManagement.tsx b/apps/studio/components/interfaces/BranchManagement/BranchManagement.tsx deleted file mode 100644 index c8637b7f443..00000000000 --- a/apps/studio/components/interfaces/BranchManagement/BranchManagement.tsx +++ /dev/null @@ -1,440 +0,0 @@ -import { PermissionAction } from '@supabase/shared-types/out/constants' -import { partition } from 'lodash' -import { GitMerge, MessageCircle } from 'lucide-react' -import { useRouter } from 'next/router' -import { useState } from 'react' -import { toast } from 'sonner' - -import { useParams } from 'common' -import { GitHubStatus } from 'components/interfaces/Settings/Integrations/GithubIntegration/GitHubStatus' -import { ScaffoldContainer, ScaffoldSection } from 'components/layouts/Scaffold' -import AlertError from 'components/ui/AlertError' -import { ButtonTooltip } from 'components/ui/ButtonTooltip' -import { DocsButton } from 'components/ui/DocsButton' -import NoPermission from 'components/ui/NoPermission' -import { useBranchDeleteMutation } from 'data/branches/branch-delete-mutation' -import { useBranchesDisableMutation } from 'data/branches/branches-disable-mutation' -import { useBranchUpdateMutation } from 'data/branches/branch-update-mutation' -import { Branch, useBranchesQuery } from 'data/branches/branches-query' -import { useGitHubConnectionsQuery } from 'data/integrations/github-connections-query' -import { useCheckPermissions } from 'hooks/misc/useCheckPermissions' -import { useSelectedOrganization } from 'hooks/misc/useSelectedOrganization' -import { useSelectedProject } from 'hooks/misc/useSelectedProject' -import { useFlag } from 'hooks/ui/useFlag' -import { useUrlState } from 'hooks/ui/useUrlState' -import { useAppStateSnapshot } from 'state/app-state' -import { Button } from 'ui' -import ConfirmationModal from 'ui-patterns/Dialogs/ConfirmationModal' -import TextConfirmModal from 'ui-patterns/Dialogs/TextConfirmModal' -import { BranchLoader, BranchManagementSection, BranchRow } from './BranchPanels' -import { BranchSelector } from './BranchSelector' -import { PreviewBranchesEmptyState, PullRequestsEmptyState } from './EmptyStates' -import { Overview } from './Overview' -import { ReviewRow } from './ReviewRow' - -type Tab = 'overview' | 'prs' | 'branches' - -const BranchManagement = () => { - const router = useRouter() - const { ref } = useParams() - const project = useSelectedProject() - const selectedOrg = useSelectedOrganization() - const gitlessBranching = useFlag('gitlessBranching') - - const hasBranchEnabled = project?.is_branch_enabled - - const isBranch = project?.parent_project_ref !== undefined - const projectRef = - project !== undefined ? (isBranch ? project.parent_project_ref : ref) : undefined - - const [urlParams, setParams] = useUrlState<{ tab: Tab }>() - const tab = urlParams.tab ?? 'overview' - const setTab = (tab: Tab) => setParams({ tab }) - - const snap = useAppStateSnapshot() - - const [showDisableBranching, setShowDisableBranching] = useState(false) - const [selectedBranchToDelete, setSelectedBranchToDelete] = useState() - - const canReadBranches = useCheckPermissions(PermissionAction.READ, 'preview_branches') - const canCreateBranches = useCheckPermissions(PermissionAction.CREATE, 'preview_branches', { - resource: { is_default: false }, - }) - - const { - data: connections, - error: connectionsError, - isLoading: isLoadingConnections, - isSuccess: isSuccessConnections, - isError: isErrorConnections, - } = useGitHubConnectionsQuery({ - organizationId: selectedOrg?.id, - }) - - const { - data: branches, - error: branchesError, - isLoading: isLoadingBranches, - isError: isErrorBranches, - isSuccess: isSuccessBranches, - } = useBranchesQuery({ projectRef }) - const [[mainBranch], previewBranchesUnsorted] = partition(branches, (branch) => branch.is_default) - const previewBranches = previewBranchesUnsorted.sort((a, b) => - new Date(a.updated_at) < new Date(b.updated_at) ? 1 : -1 - ) - const branchesWithPRs = previewBranches.filter((branch) => branch.pr_number !== undefined) - const branchesReadyForReview = previewBranches.filter( - (branch) => branch.review_requested_at !== undefined && branch.review_requested_at !== null - ) - - const currentBranch = branches?.find((branch) => branch.project_ref === ref) - const isCurrentBranchReadyForReview = - currentBranch?.review_requested_at !== undefined && currentBranch?.review_requested_at !== null - - const githubConnection = connections?.find((connection) => connection.project.ref === projectRef) - const repo = githubConnection?.repository.name ?? '' - - const isError = isErrorConnections || isErrorBranches - const isLoading = isLoadingConnections || isLoadingBranches - const isSuccess = isSuccessConnections && isSuccessBranches - - const { mutate: deleteBranch, isLoading: isDeleting } = useBranchDeleteMutation({ - onSuccess: () => { - if (selectedBranchToDelete?.project_ref === ref) { - toast.success( - 'Successfully deleted branch. You are now currently on the main branch of your project.' - ) - router.push(`/project/${projectRef}/branches`) - } else { - toast.success('Successfully deleted branch') - } - setSelectedBranchToDelete(undefined) - }, - }) - - const { mutate: disableBranching, isLoading: isDisabling } = useBranchesDisableMutation({ - onSuccess: () => { - toast.success('Successfully disabled branching for project') - setShowDisableBranching(false) - }, - }) - - const { mutate: updateBranch, isLoading: isUpdating } = useBranchUpdateMutation({ - onError: () => { - toast.error(`Failed to update the branch`) - }, - }) - - const handleMarkBranchForReview = (branch: Branch) => { - if (branch.id && projectRef) { - updateBranch( - { - id: branch.id, - projectRef, - requestReview: true, - }, - { - onSuccess: () => { - toast.success('Branch marked as ready for review') - router.push(`/project/${branch.project_ref}/merge`) - }, - } - ) - } - } - - const generateCreatePullRequestURL = (branch?: string) => { - if (githubConnection === undefined) return 'https://github.com' - - return branch !== undefined - ? `https://github.com/${githubConnection.repository.name}/compare/${mainBranch?.git_branch}...${branch}` - : `https://github.com/${githubConnection.repository.name}/compare` - } - - const onConfirmDeleteBranch = () => { - if (selectedBranchToDelete == undefined) return console.error('No branch selected') - if (projectRef == undefined) return console.error('Project ref is required') - deleteBranch({ id: selectedBranchToDelete?.id, projectRef }) - } - - const onConfirmDisableBranching = () => { - if (projectRef == undefined) return console.error('Project ref is required') - if (!previewBranches) return console.error('No branches available') - disableBranching({ projectRef, branchIds: previewBranches?.map((branch) => branch.id) }) - } - - return ( - <> - - -
-

Branches

- -
-
-
- - - -
-
- - - snap.setShowCreateBranchModal(true)} - tooltip={{ - content: { - side: 'bottom', - text: !canCreateBranches - ? 'You need additional permissions to create branches' - : undefined, - }, - }} - > - Create branch - -
-
- - {!canReadBranches ? ( - - ) : ( - <> - {isErrorConnections && ( - - )} - - {isSuccessConnections && hasBranchEnabled && } - - {isErrorBranches && tab === 'overview' && ( - - )} - - {!isError && ( - <> - {tab === 'overview' && ( - setTab('branches')} - onSelectCreateBranch={() => snap.setShowCreateBranchModal(true)} - onSelectDeleteBranch={setSelectedBranchToDelete} - generateCreatePullRequestURL={generateCreatePullRequestURL} - showProductionBranch={hasBranchEnabled} - /> - )} - {tab === 'prs' && ( -
- {gitlessBranching && ( - - - {branchesReadyForReview.length} branches ready for review - - -
- } - > - {isBranch && !isCurrentBranchReadyForReview && currentBranch && ( -
-
-
- - Mark{' '} - {currentBranch.name}{' '} - ready for review -
- -
-
- )} - {branchesReadyForReview.length > 0 ? ( - branchesReadyForReview.map((branch) => { - return - }) - ) : ( -
- No branches are currently ready for review -
- )} - - )} - - {branchesWithPRs.length > 0 ? ( - branchesWithPRs.map((branch) => { - return ( - setSelectedBranchToDelete(branch)} - /> - ) - }) - ) : ( - 0} - githubConnection={githubConnection} - gitlessBranching={gitlessBranching} - /> - )} - -
- )} - {tab === 'branches' && ( - - {isLoadingBranches && } - {isErrorBranches && ( - - )} - {isSuccessBranches && previewBranches.length === 0 && ( - snap.setShowCreateBranchModal(true)} - /> - )} - {isSuccessBranches && - previewBranches.map((branch) => { - return ( - setSelectedBranchToDelete(branch)} - /> - ) - })} - - )} - - )} - - )} -
- -
-
- - setSelectedBranchToDelete(undefined)} - onConfirm={() => onConfirmDeleteBranch()} - loading={isDeleting} - title="Delete branch" - confirmLabel="Delete branch" - confirmPlaceholder="Type in name of branch" - confirmString={selectedBranchToDelete?.name ?? ''} - alert={{ title: 'You cannot recover this branch once deleted' }} - text={ - <> - This will delete your database preview branch{' '} - {selectedBranchToDelete?.name}. - - } - /> - - onConfirmDisableBranching()} - onCancel={() => setShowDisableBranching(false)} - alert={{ - title: 'This action cannot be undone', - description: - 'All database preview branches will be removed upon disabling branching. You may still re-enable branching again thereafter, but your existing preview branches will not be restored.', - }} - > -

Before you disable branching, consider:

-
    -
  • Your project no longer requires database previews.
  • -
  • - None of your database previews are currently being used in any app. -
  • -
-
- - ) -} - -export default BranchManagement diff --git a/apps/studio/components/interfaces/BranchManagement/BranchPanels.tsx b/apps/studio/components/interfaces/BranchManagement/BranchPanels.tsx index dc7c142d066..a34c813599d 100644 --- a/apps/studio/components/interfaces/BranchManagement/BranchPanels.tsx +++ b/apps/studio/components/interfaces/BranchManagement/BranchPanels.tsx @@ -1,47 +1,13 @@ -import { PermissionAction } from '@supabase/shared-types/out/constants' import dayjs from 'dayjs' -import { - ArrowRight, - Clock, - ExternalLink, - GitPullRequest, - Github, - Infinity, - MoreVertical, - Pencil, - RefreshCw, - Shield, - Trash2, -} from 'lucide-react' -import Link from 'next/link' -import { PropsWithChildren, ReactNode, useState } from 'react' -import { useInView } from 'react-intersection-observer' -import { toast } from 'sonner' +import { Github } from 'lucide-react' +import { useRouter } from 'next/router' +import { PropsWithChildren, ReactNode } from 'react' -import { useParams } from 'common' import { ButtonTooltip } from 'components/ui/ButtonTooltip' import ShimmeringLoader from 'components/ui/ShimmeringLoader' -import { useBranchQuery } from 'data/branches/branch-query' -import { useBranchResetMutation } from 'data/branches/branch-reset-mutation' -import { useBranchUpdateMutation } from 'data/branches/branch-update-mutation' import type { Branch } from 'data/branches/branches-query' -import { useCheckPermissions } from 'hooks/misc/useCheckPermissions' -import { useFlag } from 'hooks/ui/useFlag' -import { - Badge, - Button, - DropdownMenu, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuTrigger, - Tooltip, - TooltipContent, - TooltipTrigger, -} from 'ui' -import ConfirmationModal from 'ui-patterns/Dialogs/ConfirmationModal' -import BranchStatusBadge from './BranchStatusBadge' -import { EditBranchModal } from './EditBranchModal' -import WorkflowLogs from './WorkflowLogs' +import { Tooltip, TooltipContent, TooltipTrigger } from 'ui' +import { WorkflowLogs } from './WorkflowLogs' interface BranchManagementSectionProps { header: string | ReactNode @@ -55,7 +21,7 @@ export const BranchManagementSection = ({ }: PropsWithChildren) => { return (
-
+
{typeof header === 'string' ? {header} : header}
{children}
@@ -93,344 +59,72 @@ export const BranchLoader = () => { interface BranchRowProps { repo: string + label?: string | ReactNode branch: Branch - isMain?: boolean - generateCreatePullRequestURL?: (branchName?: string) => string - onSelectDeleteBranch: () => void + rowLink?: string + external?: boolean + rowActions?: ReactNode } export const BranchRow = ({ branch, - isMain = false, + label, repo, - generateCreatePullRequestURL, - onSelectDeleteBranch, + rowLink, + external = false, + rowActions, }: BranchRowProps) => { - const { ref: projectRef } = useParams() - const isActive = projectRef === branch?.project_ref - const gitlessBranching = useFlag('gitlessBranching') - - const canDeleteBranches = useCheckPermissions(PermissionAction.DELETE, 'preview_branches') - const canUpdateBranches = useCheckPermissions(PermissionAction.UPDATE, 'preview_branches') + const router = useRouter() const daysFromNow = dayjs().diff(dayjs(branch.updated_at), 'day') const formattedTimeFromNow = dayjs(branch.updated_at).fromNow() const formattedUpdatedAt = dayjs(branch.updated_at).format('DD MMM YYYY, HH:mm:ss (ZZ)') - const createPullRequestURL = - generateCreatePullRequestURL?.(branch.git_branch) ?? 'https://github.com' + const navigateUrl = rowLink ?? `/project/${branch.project_ref}` - const { ref, inView } = useInView() - const { data } = useBranchQuery( - { projectRef, id: branch.id }, - { - enabled: inView, - refetchInterval(data) { - if (data?.status !== 'ACTIVE_HEALTHY') { - return 1000 * 3 // 3 seconds - } - - return false - }, + const handleRowClick = () => { + if (external) { + window.open(navigateUrl, '_blank', 'noopener noreferrer') + } else { + router.push(navigateUrl) } - ) - - const isBranchActiveHealthy = data?.status === 'ACTIVE_HEALTHY' - - const [showConfirmResetModal, setShowConfirmResetModal] = useState(false) - const [showBranchModeSwitch, setShowBranchModeSwitch] = useState(false) - const [showEditBranchModal, setShowEditBranchModal] = useState(false) - - const { mutate: updateBranch, isLoading: isUpdating } = useBranchUpdateMutation({ - onSuccess() { - toast.success('Successfully updated branch') - setShowBranchModeSwitch(false) - }, - }) - - const onUpdateBranchPersistentMode = () => { - if (projectRef == undefined) return console.error('Project ref is required') - updateBranch({ id: branch.id, projectRef, persistent: !branch.persistent }) - } - - const { mutate: resetBranch, isLoading: isResetting } = useBranchResetMutation({ - onSuccess() { - toast.success('Success! Please allow a few seconds for the branch to reset.') - setShowConfirmResetModal(false) - }, - }) - - function onConfirmReset() { - if (!projectRef) throw new Error('Invalid project reference') - resetBranch({ id: branch.id, projectRef }) } return ( -
+
+
+ {branch.git_branch && ( + + + + + + )} + + +
+ {label || branch.name} +
+
+ Switch to branch +
+
- - ) : branch.persistent ? ( - - ) : null - } - tooltip={{ - content: { - side: 'bottom', - text: branch.persistent - ? `${branch.name} is a persistent branch and will remain active even after the - underlying PR is closed` - : 'Switch to branch', - }, - }} - > - - {branch.name} - - - - {branch.git_branch && } - - {isActive && Current} -

{daysFromNow > 1 ? `Updated on ${formattedUpdatedAt}` : `Updated ${formattedTimeFromNow}`}

+ + {rowActions}
-
- {isMain ? ( -
- {repo && ( - - )} - - - -
- ) : ( -
- {branch.git_branch && branch.pr_number === undefined ? ( - - ) : branch.pr_number !== undefined ? ( -
- - #{branch.pr_number} - - - -
- ) : null} - - - -
- )} -
- - setShowEditBranchModal(false)} - />
) } diff --git a/apps/studio/components/interfaces/BranchManagement/BranchSelector.tsx b/apps/studio/components/interfaces/BranchManagement/BranchSelector.tsx index 3c9b4c277c1..51c2c5528e1 100644 --- a/apps/studio/components/interfaces/BranchManagement/BranchSelector.tsx +++ b/apps/studio/components/interfaces/BranchManagement/BranchSelector.tsx @@ -1,9 +1,9 @@ -import { Check, Plus, Shield } from 'lucide-react' +import { Check, GitMerge, Shield } from 'lucide-react' import { useState } from 'react' +import { ButtonTooltip } from 'components/ui/ButtonTooltip' import { Branch } from 'data/branches/branches-query' import { - Button, CommandEmpty_Shadcn_, CommandGroup_Shadcn_, CommandInput_Shadcn_, @@ -21,6 +21,7 @@ interface BranchSelectorProps { onBranchSelected?: (branch: Branch) => void disabled?: boolean isUpdating?: boolean + type?: 'primary' | 'outline' } export const BranchSelector = ({ @@ -28,6 +29,7 @@ export const BranchSelector = ({ onBranchSelected, disabled = false, isUpdating = false, + type = 'primary', }: BranchSelectorProps) => { const [open, setOpen] = useState(false) const [selectedBranch, setSelectedBranch] = useState(null) @@ -46,16 +48,24 @@ export const BranchSelector = ({ return ( - + {isUpdating ? 'Creating...' : 'New merge request'} + - + diff --git a/apps/studio/components/interfaces/BranchManagement/CreateBranchModal.tsx b/apps/studio/components/interfaces/BranchManagement/CreateBranchModal.tsx index 92a6a77db2f..fca9784bed0 100644 --- a/apps/studio/components/interfaces/BranchManagement/CreateBranchModal.tsx +++ b/apps/studio/components/interfaces/BranchManagement/CreateBranchModal.tsx @@ -1,14 +1,15 @@ import { zodResolver } from '@hookform/resolvers/zod' import { useQueryClient } from '@tanstack/react-query' -import { useParams } from 'common' -import { DollarSign, Github, GitMerge, Loader2, Merge } from 'lucide-react' +import { DollarSign, GitMerge, Github, Loader2 } from 'lucide-react' import Image from 'next/image' import Link from 'next/link' +import { useRouter } from 'next/router' import { useEffect } from 'react' import { useForm } from 'react-hook-form' import { toast } from 'sonner' import * as z from 'zod' +import { useParams } from 'common' import { BranchingPITRNotice } from 'components/layouts/AppLayout/EnableBranchingButton/BranchingPITRNotice' import AlertError from 'components/ui/AlertError' import { GenericSkeletonLoader } from 'components/ui/ShimmeringLoader' @@ -42,7 +43,6 @@ import { cn, } from 'ui' import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout' -import { useRouter } from 'next/router' export const CreateBranchModal = () => { const { ref } = useParams() diff --git a/apps/studio/components/interfaces/BranchManagement/EdgeFunctionsDiffPanel.tsx b/apps/studio/components/interfaces/BranchManagement/EdgeFunctionsDiffPanel.tsx index eb003f42937..6bcd2ba8bbb 100644 --- a/apps/studio/components/interfaces/BranchManagement/EdgeFunctionsDiffPanel.tsx +++ b/apps/studio/components/interfaces/BranchManagement/EdgeFunctionsDiffPanel.tsx @@ -142,7 +142,9 @@ const EdgeFunctionsDiffPanel = ({ return } - if (!diffResults.hasChanges) { + const noChanges = diffResults.addedSlugs.length === 0 && diffResults.modifiedSlugs.length === 0 + + if (noChanges) { return (
diff --git a/apps/studio/components/interfaces/BranchManagement/EditBranchModal.tsx b/apps/studio/components/interfaces/BranchManagement/EditBranchModal.tsx index 2ab15c74e3d..7161c91db31 100644 --- a/apps/studio/components/interfaces/BranchManagement/EditBranchModal.tsx +++ b/apps/studio/components/interfaces/BranchManagement/EditBranchModal.tsx @@ -219,7 +219,7 @@ export const EditBranchModal = ({ branch, visible, onClose }: EditBranchModalPro control={form.control} name="branchName" render={({ field }) => ( - + { - const snap = useAppStateSnapshot() - - const canCreateBranches = useCheckPermissions(PermissionAction.CREATE, 'preview_branches', { - resource: { is_default: false }, - }) - - return ( - -

- Create preview branches to experiment with changes to your database schema in a safe, - non-destructive environment. -

-
- } - onClick={() => snap.setShowCreateBranchModal(true)} - tooltip={{ - content: { - side: 'bottom', - text: !canCreateBranches - ? 'You need additional permissions to create branches' - : undefined, - }, - }} - > - Create branch - - - -
-
- ) -} - export const PullRequestsEmptyState = ({ url, projectRef, - hasBranches, + branches, + onBranchSelected, + isUpdating, githubConnection, - gitlessBranching, + gitlessBranching = false, }: { url: string projectRef: string - hasBranches: boolean + branches: Branch[] + onBranchSelected: (branch: Branch) => void + isUpdating: boolean githubConnection?: any - gitlessBranching?: boolean + gitlessBranching: boolean }) => { - // Show GitHub connection message if GitHub is not connected and gitless branching is enabled - if (!githubConnection && gitlessBranching) { - return ( -
-
-

Connect to GitHub for seamless branching

-

- Sync GitHub repos to Supabase projects for automatic branch creation and merging -

-
- - -
- ) - } - return (
-

No pull requests made yet for this repository

+

No merge requests

- Only pull requests with the ./migration directory changes will show here. + Create your first merge request to merge changes back to the main branch

- {hasBranches && ( -
-
-
- -

Create a pull request

-
- -
-
-
-

Not sure what to do?

-

Browse our documentation

-
- -
-
- )} +
+ {githubConnection ? ( + + ) : ( + + )} + {gitlessBranching && ( + + )} +
) } @@ -137,15 +67,7 @@ export const PreviewBranchesEmptyState = ({ non-destructive environment.

- + diff --git a/apps/studio/components/interfaces/BranchManagement/Overview.tsx b/apps/studio/components/interfaces/BranchManagement/Overview.tsx index 487ad934292..dc3a9dfcbf0 100644 --- a/apps/studio/components/interfaces/BranchManagement/Overview.tsx +++ b/apps/studio/components/interfaces/BranchManagement/Overview.tsx @@ -1,20 +1,47 @@ -import { noop } from 'lodash' -import { Button } from 'ui' +import { PermissionAction } from '@supabase/shared-types/out/constants' +import { useQueryClient } from '@tanstack/react-query' +import { partition } from 'lodash' +import { + Clock, + ExternalLink, + Infinity, + MoreVertical, + Pencil, + RefreshCw, + Shield, + Trash2, +} from 'lucide-react' +import Link from 'next/link' +import { useState } from 'react' +import { toast } from 'sonner' +import { useParams } from 'common' +import { DropdownMenuItemTooltip } from 'components/ui/DropdownMenuItemTooltip' +import { useBranchQuery } from 'data/branches/branch-query' +import { useBranchResetMutation } from 'data/branches/branch-reset-mutation' +import { useBranchUpdateMutation } from 'data/branches/branch-update-mutation' import type { Branch } from 'data/branches/branches-query' -import { ChevronRight } from 'lucide-react' +import { branchKeys } from 'data/branches/keys' +import { useCheckPermissions } from 'hooks/misc/useCheckPermissions' +import { useFlag } from 'hooks/ui/useFlag' +import { + Button, + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from 'ui' +import ConfirmationModal from 'ui-patterns/Dialogs/ConfirmationModal' import { BranchLoader, BranchManagementSection, BranchRow, BranchRowLoader } from './BranchPanels' +import { EditBranchModal } from './EditBranchModal' import { PreviewBranchesEmptyState } from './EmptyStates' -const MAX_BRANCHES_OVERVIEW = 10 - interface OverviewProps { isLoading: boolean isSuccess: boolean repo: string mainBranch: Branch previewBranches: Branch[] - onViewAllBranches: () => void onSelectCreateBranch: () => void onSelectDeleteBranch: (branch: Branch) => void generateCreatePullRequestURL: (branchName?: string) => string @@ -27,48 +54,89 @@ export const Overview = ({ repo, mainBranch, previewBranches, - onViewAllBranches, onSelectCreateBranch, onSelectDeleteBranch, generateCreatePullRequestURL, showProductionBranch = true, }: OverviewProps) => { + const [persistentBranches, ephemeralBranches] = partition( + previewBranches, + (branch) => branch.persistent + ) + return ( <> {showProductionBranch && ( {isLoading && } {isSuccess && mainBranch !== undefined && ( - + + + {mainBranch.name} +
+ } + repo={repo} + rowActions={} + /> )} )} - - -
- ) - } - > + {/* Persistent Branches Section */} + {isLoading && } - {isSuccess && previewBranches.length === 0 && ( - + {isSuccess && persistentBranches.length === 0 && ( +
+

No persistent branches

+

+ Persistent branches are long-lived and not automatically deleted. +

+
)} {isSuccess && - previewBranches.slice(0, MAX_BRANCHES_OVERVIEW).map((branch) => { + persistentBranches.map((branch) => { return ( onSelectDeleteBranch(branch)} + rowActions={ + onSelectDeleteBranch(branch)} + generateCreatePullRequestURL={generateCreatePullRequestURL} + /> + } + /> + ) + })} +
+ + {/* Ephemeral/Preview Branches Section */} + + {isLoading && } + {isSuccess && ephemeralBranches.length === 0 && ( + + )} + {isSuccess && + ephemeralBranches.map((branch) => { + return ( + onSelectDeleteBranch(branch)} + generateCreatePullRequestURL={generateCreatePullRequestURL} + /> + } /> ) })} @@ -76,3 +144,267 @@ export const Overview = ({ ) } + +// Row actions for preview branches (non-main) +const PreviewBranchActions = ({ + branch, + onSelectDeleteBranch, + generateCreatePullRequestURL, +}: { + branch: Branch + repo: string + onSelectDeleteBranch: () => void + generateCreatePullRequestURL: (branchName?: string) => string +}) => { + const gitlessBranching = useFlag('gitlessBranching') + const queryClient = useQueryClient() + const projectRef = branch.parent_project_ref ?? branch.project_ref + + const canDeleteBranches = useCheckPermissions(PermissionAction.DELETE, 'preview_branches') + const canUpdateBranches = useCheckPermissions(PermissionAction.UPDATE, 'preview_branches') + + const { data } = useBranchQuery({ projectRef, id: branch.id }) + const isBranchActiveHealthy = data?.status === 'ACTIVE_HEALTHY' + + const [showConfirmResetModal, setShowConfirmResetModal] = useState(false) + const [showBranchModeSwitch, setShowBranchModeSwitch] = useState(false) + const [showEditBranchModal, setShowEditBranchModal] = useState(false) + + const { mutate: resetBranch, isLoading: isResetting } = useBranchResetMutation({ + onSuccess() { + toast.success('Success! Please allow a few seconds for the branch to reset.') + setShowConfirmResetModal(false) + }, + }) + + const { mutate: updateBranch, isLoading: isUpdatingBranch } = useBranchUpdateMutation({ + onSuccess() { + toast.success('Successfully updated branch') + setShowBranchModeSwitch(false) + if (projectRef) { + queryClient.invalidateQueries({ queryKey: branchKeys.list(projectRef) }) + } + }, + }) + + const onConfirmReset = () => { + if (!projectRef) return + resetBranch({ id: branch.id, projectRef }) + } + + const onTogglePersistent = () => { + if (!projectRef) return + updateBranch({ id: branch.id, projectRef, persistent: !branch.persistent }) + } + + return ( + <> + + + + Workflow Logs - Select a workflow run to view logs @@ -143,5 +161,3 @@ const WorkflowLogs = ({ projectRef }: WorkflowLogsProps) => { ) } - -export default WorkflowLogs diff --git a/apps/studio/components/interfaces/Settings/Integrations/GithubIntegration/GitHubStatus.tsx b/apps/studio/components/interfaces/Settings/Integrations/GithubIntegration/GitHubStatus.tsx index b120389a4e4..c525ced88a6 100644 --- a/apps/studio/components/interfaces/Settings/Integrations/GithubIntegration/GitHubStatus.tsx +++ b/apps/studio/components/interfaces/Settings/Integrations/GithubIntegration/GitHubStatus.tsx @@ -1,6 +1,6 @@ -import { useRouter } from 'next/router' -import { CheckCircle2, Settings, AlertCircle } from 'lucide-react' +import { AlertCircle, ArrowUpRight, CheckCircle2 } from 'lucide-react' import Image from 'next/image' +import Link from 'next/link' import { useParams } from 'common' import { useBranchesQuery } from 'data/branches/branches-query' @@ -8,8 +8,7 @@ import { useGitHubConnectionsQuery } from 'data/integrations/github-connections- import { useSelectedOrganization } from 'hooks/misc/useSelectedOrganization' import { useSelectedProject } from 'hooks/misc/useSelectedProject' import { BASE_PATH } from 'lib/constants' -import { Button, Card, CardContent } from 'ui' -import Link from 'next/link' +import { HoverCard, HoverCardContent, HoverCardTrigger } from 'ui' export const GitHubStatus = () => { const { ref: projectRef } = useParams() @@ -33,13 +32,42 @@ export const GitHubStatus = () => { const mainBranch = branches?.find((branch) => branch.is_default) const isConnected = Boolean(githubConnection) + const hasGitBranchSync = Boolean(mainBranch?.git_branch?.trim()) const hasAutomaticBranching = Boolean(githubConnection?.new_branch_per_pr) return ( - - -
+ + + +
+

GitHub Integration

+ +
+

+ {isConnected ? ( + <> + GitHub + {githubConnection?.repository.name} + + ) : ( + 'Not connected' + )} +

+ +
+ + +
{ height={20} alt="GitHub" /> - -
-

- {isConnected - ? `Connected to ${githubConnection?.repository.name}` - : 'GitHub Integration'} -

- -
- {isConnected ? ( - <> -
- {hasGitBranchSync ? ( - - ) : ( - - )} - - {hasGitBranchSync - ? `Syncing production (${mainBranch?.git_branch})` - : 'Production sync disabled'} - -
-
- {hasAutomaticBranching ? ( - - ) : ( - - )} - - {hasAutomaticBranching - ? 'Automatically creating branches' - : 'Automatic branching disabled'} - -
- - ) : ( - Not connected to any repository - )} -
-
+ + {isConnected ? githubConnection?.repository.name : 'Not connected'} +
- - - + {isConnected ? ( +
+
+ {hasGitBranchSync ? ( + + ) : ( + + )} + + {hasGitBranchSync + ? `Syncing production (${mainBranch?.git_branch})` + : 'Production sync disabled'} + +
+
+ {hasAutomaticBranching ? ( + + ) : ( + + )} + + {hasAutomaticBranching + ? 'Automatically creating branches' + : 'Automatic branching disabled'} + +
+
+ ) : ( +

Not connected to any repository

+ )} +
+
) } diff --git a/apps/studio/components/layouts/AppLayout/BranchDropdown.tsx b/apps/studio/components/layouts/AppLayout/BranchDropdown.tsx index af8a2f25e6c..21266c321a1 100644 --- a/apps/studio/components/layouts/AppLayout/BranchDropdown.tsx +++ b/apps/studio/components/layouts/AppLayout/BranchDropdown.tsx @@ -2,7 +2,6 @@ import { AlertCircle, Check, ChevronsUpDown, - GitMerge, ListTree, MessageCircle, Plus, @@ -15,7 +14,6 @@ import { useState } from 'react' import { useParams } from 'common' import ShimmeringLoader from 'components/ui/ShimmeringLoader' import { Branch, useBranchesQuery } from 'data/branches/branches-query' -import { useBranchUpdateMutation } from 'data/branches/branch-update-mutation' import { useSelectedProject } from 'hooks/misc/useSelectedProject' import { useAppStateSnapshot } from 'state/app-state' import { @@ -35,8 +33,6 @@ import { cn, } from 'ui' import { sanitizeRoute } from './ProjectDropdown' -import { useFlag } from 'hooks/ui/useFlag' -import { toast } from 'sonner' const BranchLink = ({ branch, @@ -78,22 +74,13 @@ const BranchLink = ({ export const BranchDropdown = () => { const router = useRouter() const { ref } = useParams() - const gitlessBranching = useFlag('gitlessBranching') - const projectDetails = useSelectedProject() const snap = useAppStateSnapshot() + const projectDetails = useSelectedProject() + + const [open, setOpen] = useState(false) const projectRef = projectDetails?.parent_project_ref || ref - const { mutate: updateBranch, isLoading: isUpdatingBranch } = useBranchUpdateMutation({ - onSuccess: (_, variables) => { - if (variables.requestReview) { - router.push(`/project/${ref}/merge`) - } - setOpen(false) - toast.success('Branch marked as ready for review') - }, - }) - const { data: branches, isLoading, @@ -122,135 +109,10 @@ export const BranchDropdown = () => { ? [mainBranch].concat(restOfBranches ?? []) : restOfBranches ?? [] : [defaultMainBranch] + const branchList = isBranchingEnabled ? sortedBranches ?? [] : [defaultMainBranch] const BRANCHING_GITHUB_DISCUSSION_LINK = 'https://github.com/orgs/supabase/discussions/18937' - const [open, setOpen] = useState(false) - - const MenuContent = ({ - branchList, - showSearch, - }: { - branchList: Branch[] - showSearch?: boolean - }) => ( - - {showSearch && } - - {showSearch && No branches found} - - - - {branchList.map((branch) => ( - - ))} - - - - - - - { - setOpen(false) - snap.setShowCreateBranchModal(true) - }} - onClick={() => { - setOpen(false) - snap.setShowCreateBranchModal(true) - }} - > -
- -

Create branch

-
-
- {gitlessBranching && - isBranchingEnabled && - selectedBranch && - !selectedBranch.is_default && ( - <> - { - if (selectedBranch?.review_requested_at) { - router.push(`/project/${ref}/merge`) - } else { - if (selectedBranch?.id && projectRef) { - updateBranch({ - id: selectedBranch.id, - projectRef, - requestReview: true, - }) - } - } - }} - onClick={() => setOpen(false)} - > -
- - {isUpdatingBranch - ? 'Updating...' - : selectedBranch?.review_requested_at - ? 'Review changes' - : 'Ready for review'} -
-
- - )} - { - setOpen(false) - router.push(`/project/${ref}/branches`) - }} - onClick={() => setOpen(false)} - > - - -

Manage branches

- -
-
- - - - - { - setOpen(false) - window?.open(BRANCHING_GITHUB_DISCUSSION_LINK, '_blank')?.focus() - }} - onClick={() => setOpen(false)} - > - { - setOpen(false) - }} - className="w-full flex gap-2" - > - -
-

Branching feedback

-

Join Github Discussion

-
- -
-
-
-
- ) - return ( <> {isLoading && } @@ -288,10 +150,91 @@ export const BranchDropdown = () => { /> - + + {isBranchingEnabled && } + + {isBranchingEnabled && ( + No branches found + )} + + + + {branchList.map((branch) => ( + + ))} + + + + + + + { + setOpen(false) + snap.setShowCreateBranchModal(true) + }} + onClick={() => { + setOpen(false) + snap.setShowCreateBranchModal(true) + }} + > +
+ +

Create branch

+
+
+ { + setOpen(false) + router.push(`/project/${ref}/branches`) + }} + onClick={() => setOpen(false)} + > + + +

Manage branches

+ +
+
+ + + + + { + setOpen(false) + window?.open(BRANCHING_GITHUB_DISCUSSION_LINK, '_blank')?.focus() + }} + onClick={() => setOpen(false)} + > + setOpen(false)} + className="w-full flex gap-2" + > + +
+

Branching feedback

+

Join Github Discussion

+
+
+
+
+
+
diff --git a/apps/studio/components/layouts/BranchLayout/BranchLayout.tsx b/apps/studio/components/layouts/BranchLayout/BranchLayout.tsx new file mode 100644 index 00000000000..9ce4fb7d2f7 --- /dev/null +++ b/apps/studio/components/layouts/BranchLayout/BranchLayout.tsx @@ -0,0 +1,40 @@ +import { useRouter } from 'next/router' +import { PropsWithChildren } from 'react' + +import { useParams } from 'common' +import { GitHubStatus } from 'components/interfaces/Settings/Integrations/GithubIntegration/GitHubStatus' +import { ProductMenu } from 'components/ui/ProductMenu' +import { withAuth } from 'hooks/misc/withAuth' +import ProjectLayout from '../ProjectLayout/ProjectLayout' +import { generateBranchMenu } from './BranchLayout.utils' + +const BranchProductMenu = () => { + const router = useRouter() + const { ref: projectRef = 'default' } = useParams() + const page = router.pathname.split('/')[4] ?? 'branches' + + return ( + <> + +
+

Configure

+ +
+ + ) +} + +const BranchLayout = ({ children }: PropsWithChildren<{}>) => { + return ( + } + isBlocking={false} + > + {children} + + ) +} + +export default withAuth(BranchLayout) diff --git a/apps/studio/components/layouts/BranchLayout/BranchLayout.utils.ts b/apps/studio/components/layouts/BranchLayout/BranchLayout.utils.ts new file mode 100644 index 00000000000..40785ed8827 --- /dev/null +++ b/apps/studio/components/layouts/BranchLayout/BranchLayout.utils.ts @@ -0,0 +1,23 @@ +import type { ProductMenuGroup } from 'components/ui/ProductMenu/ProductMenu.types' + +export const generateBranchMenu = (ref: string): ProductMenuGroup[] => { + return [ + { + title: 'Manage', + items: [ + { + name: 'Branches', + key: 'branches', + url: `/project/${ref}/branches`, + items: [], + }, + { + name: 'Merge requests', + key: 'merge-requests', + url: `/project/${ref}/branches/merge-requests`, + items: [], + }, + ], + }, + ] +} diff --git a/apps/studio/components/layouts/ProjectLayout/ProjectLayout.tsx b/apps/studio/components/layouts/ProjectLayout/ProjectLayout.tsx index 25991f5b885..fa41c5735f3 100644 --- a/apps/studio/components/layouts/ProjectLayout/ProjectLayout.tsx +++ b/apps/studio/components/layouts/ProjectLayout/ProjectLayout.tsx @@ -86,7 +86,7 @@ const ProjectLayout = forwardRef } - if (requiresDbConnection && isProjectBuilding) { + if (requiresDbConnection && isProjectBuilding && !isBranchesPage) { return } diff --git a/apps/studio/pages/project/[ref]/branches.tsx b/apps/studio/pages/project/[ref]/branches.tsx deleted file mode 100644 index 355bee32e2a..00000000000 --- a/apps/studio/pages/project/[ref]/branches.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import BranchManagement from 'components/interfaces/BranchManagement/BranchManagement' -import { ProjectLayoutWithAuth } from 'components/layouts/ProjectLayout/ProjectLayout' -import DefaultLayout from 'components/layouts/DefaultLayout' -import type { NextPageWithLayout } from 'types' - -const BranchManagementPage: NextPageWithLayout = () => { - return -} - -BranchManagementPage.getLayout = (page) => ( - - {page} - -) - -export default BranchManagementPage diff --git a/apps/studio/pages/project/[ref]/branches/index.tsx b/apps/studio/pages/project/[ref]/branches/index.tsx new file mode 100644 index 00000000000..fdff72d9dbc --- /dev/null +++ b/apps/studio/pages/project/[ref]/branches/index.tsx @@ -0,0 +1,232 @@ +import { PermissionAction } from '@supabase/shared-types/out/constants' +import { partition } from 'lodash' +import { MessageCircle } from 'lucide-react' +import { useRouter } from 'next/router' +import { useState } from 'react' +import { toast } from 'sonner' + +import { useParams } from 'common' +import { Overview } from 'components/interfaces/BranchManagement/Overview' +import BranchLayout from 'components/layouts/BranchLayout/BranchLayout' +import DefaultLayout from 'components/layouts/DefaultLayout' +import { PageLayout } from 'components/layouts/PageLayout/PageLayout' +import { ScaffoldContainer, ScaffoldSection } from 'components/layouts/Scaffold' +import AlertError from 'components/ui/AlertError' +import { ButtonTooltip } from 'components/ui/ButtonTooltip' +import { DocsButton } from 'components/ui/DocsButton' +import NoPermission from 'components/ui/NoPermission' +import { useBranchDeleteMutation } from 'data/branches/branch-delete-mutation' +import { Branch, useBranchesQuery } from 'data/branches/branches-query' +import { useGitHubConnectionsQuery } from 'data/integrations/github-connections-query' +import { useCheckPermissions } from 'hooks/misc/useCheckPermissions' +import { useSelectedOrganization } from 'hooks/misc/useSelectedOrganization' +import { useSelectedProject } from 'hooks/misc/useSelectedProject' +import { useAppStateSnapshot } from 'state/app-state' +import type { NextPageWithLayout } from 'types' +import { Button } from 'ui' +import TextConfirmModal from 'ui-patterns/Dialogs/TextConfirmModal' + +const BranchesPage: NextPageWithLayout = () => { + const router = useRouter() + const { ref } = useParams() + const snap = useAppStateSnapshot() + const project = useSelectedProject() + const selectedOrg = useSelectedOrganization() + + const [selectedBranchToDelete, setSelectedBranchToDelete] = useState() + + const hasBranchEnabled = project?.is_branch_enabled + + const isBranch = project?.parent_project_ref !== undefined + const projectRef = + project !== undefined ? (isBranch ? project.parent_project_ref : ref) : undefined + + const canReadBranches = useCheckPermissions(PermissionAction.READ, 'preview_branches') + + const { + data: connections, + error: connectionsError, + isLoading: isLoadingConnections, + isSuccess: isSuccessConnections, + isError: isErrorConnections, + } = useGitHubConnectionsQuery({ + organizationId: selectedOrg?.id, + }) + + const { + data: branches, + error: branchesError, + isLoading: isLoadingBranches, + isError: isErrorBranches, + isSuccess: isSuccessBranches, + } = useBranchesQuery({ projectRef }) + const [[mainBranch], previewBranchesUnsorted] = partition(branches, (branch) => branch.is_default) + const previewBranches = previewBranchesUnsorted.sort((a, b) => + new Date(a.updated_at) < new Date(b.updated_at) ? 1 : -1 + ) + + const githubConnection = connections?.find((connection) => connection.project.ref === projectRef) + const repo = githubConnection?.repository.name ?? '' + + const isError = isErrorConnections || isErrorBranches + const isLoading = isLoadingConnections || isLoadingBranches + const isSuccess = isSuccessConnections && isSuccessBranches + + const { mutate: deleteBranch, isLoading: isDeleting } = useBranchDeleteMutation({ + onSuccess: () => { + toast.success('Successfully deleted branch') + setSelectedBranchToDelete(undefined) + }, + }) + + const generateCreatePullRequestURL = (branch?: string) => { + if (githubConnection === undefined) return 'https://github.com' + + return branch !== undefined + ? `https://github.com/${githubConnection.repository.name}/compare/${mainBranch?.git_branch}...${branch}` + : `https://github.com/${githubConnection.repository.name}/compare` + } + + const onConfirmDeleteBranch = () => { + if (selectedBranchToDelete == undefined) return console.error('No branch selected') + if (projectRef == undefined) return console.error('Project ref is required') + deleteBranch( + { id: selectedBranchToDelete?.id, projectRef }, + { + onSuccess: () => { + if (selectedBranchToDelete.project_ref === ref) { + router.push(`/project/${selectedBranchToDelete.parent_project_ref}/branches`) + } + }, + } + ) + } + + return ( + <> + + +
+
+ {!canReadBranches ? ( + + ) : ( + <> + {isErrorConnections && ( + + )} + + {isErrorBranches && ( + + )} + + {!isError && ( + snap.setShowCreateBranchModal(true)} + onSelectDeleteBranch={setSelectedBranchToDelete} + generateCreatePullRequestURL={generateCreatePullRequestURL} + showProductionBranch={hasBranchEnabled} + /> + )} + + )} +
+
+
+
+ + setSelectedBranchToDelete(undefined)} + onConfirm={() => onConfirmDeleteBranch()} + loading={isDeleting} + title="Delete branch" + confirmLabel="Delete branch" + confirmPlaceholder="Type in name of branch" + confirmString={selectedBranchToDelete?.name ?? ''} + alert={{ title: 'You cannot recover this branch once deleted' }} + text={ + <> + This will delete your database preview branch{' '} + {selectedBranchToDelete?.name}. + + } + /> + + ) +} + +BranchesPage.getLayout = (page) => { + const BranchesPageWrapper = () => { + const snap = useAppStateSnapshot() + const canCreateBranches = useCheckPermissions(PermissionAction.CREATE, 'preview_branches', { + resource: { is_default: false }, + }) + + const primaryActions = ( + snap.setShowCreateBranchModal(true)} + tooltip={{ + content: { + side: 'bottom', + text: !canCreateBranches + ? 'You need additional permissions to create branches' + : undefined, + }, + }} + > + Create branch + + ) + + const secondaryActions = ( + + ) + + return ( + + {page} + + ) + } + + return ( + + + + + + ) +} + +export default BranchesPage diff --git a/apps/studio/pages/project/[ref]/branches/merge-requests.tsx b/apps/studio/pages/project/[ref]/branches/merge-requests.tsx new file mode 100644 index 00000000000..7aed9e003b4 --- /dev/null +++ b/apps/studio/pages/project/[ref]/branches/merge-requests.tsx @@ -0,0 +1,364 @@ +import { PermissionAction } from '@supabase/shared-types/out/constants' +import { partition } from 'lodash' +import { ArrowRight, GitMerge, MessageCircle, MoreVertical, Shield, X } from 'lucide-react' +import { useRouter } from 'next/router' +import { PropsWithChildren } from 'react' +import { toast } from 'sonner' + +import { useParams } from 'common' +import { + BranchManagementSection, + BranchRow, +} from 'components/interfaces/BranchManagement/BranchPanels' +import { BranchSelector } from 'components/interfaces/BranchManagement/BranchSelector' +import { PullRequestsEmptyState } from 'components/interfaces/BranchManagement/EmptyStates' +import BranchLayout from 'components/layouts/BranchLayout/BranchLayout' +import DefaultLayout from 'components/layouts/DefaultLayout' +import { PageLayout } from 'components/layouts/PageLayout/PageLayout' +import { ScaffoldContainer, ScaffoldSection } from 'components/layouts/Scaffold' +import AlertError from 'components/ui/AlertError' +import { DocsButton } from 'components/ui/DocsButton' +import NoPermission from 'components/ui/NoPermission' +import { useBranchUpdateMutation } from 'data/branches/branch-update-mutation' +import { Branch, useBranchesQuery } from 'data/branches/branches-query' +import { useGitHubConnectionsQuery } from 'data/integrations/github-connections-query' +import { useCheckPermissions } from 'hooks/misc/useCheckPermissions' +import { useSelectedOrganization } from 'hooks/misc/useSelectedOrganization' +import { useSelectedProject } from 'hooks/misc/useSelectedProject' +import { useFlag } from 'hooks/ui/useFlag' +import type { NextPageWithLayout } from 'types' +import { + Button, + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, + Tooltip, +} from 'ui' +import { GenericSkeletonLoader } from 'ui-patterns/ShimmeringLoader' + +const MergeRequestsPage: NextPageWithLayout = () => { + const router = useRouter() + const { ref } = useParams() + const project = useSelectedProject() + const selectedOrg = useSelectedOrganization() + const gitlessBranching = useFlag('gitlessBranching') + + const isBranch = project?.parent_project_ref !== undefined + const projectRef = + project !== undefined ? (isBranch ? project.parent_project_ref : ref) : undefined + + const canReadBranches = useCheckPermissions(PermissionAction.READ, 'preview_branches') + + const { + data: connections, + error: connectionsError, + isError: isErrorConnections, + } = useGitHubConnectionsQuery({ + organizationId: selectedOrg?.id, + }) + + const { + data: branches = [], + error: branchesError, + isLoading: isLoadingBranches, + isError: isErrorBranches, + } = useBranchesQuery({ projectRef }) + const [[mainBranch], previewBranchesUnsorted] = partition(branches, (branch) => branch.is_default) + const previewBranches = previewBranchesUnsorted.sort((a, b) => + new Date(a.updated_at) < new Date(b.updated_at) ? 1 : -1 + ) + + const mergeRequestBranches = previewBranches.filter( + (branch) => + branch.pr_number !== undefined || + (branch.review_requested_at !== undefined && branch.review_requested_at !== null) + ) + + const currentBranch = branches?.find((branch) => branch.project_ref === ref) + const isCurrentBranchReadyForReview = !!currentBranch?.review_requested_at + + const githubConnection = connections?.find((connection) => connection.project.ref === projectRef) + const repo = githubConnection?.repository.name ?? '' + + const isError = isErrorConnections || isErrorBranches + + const { mutate: updateBranch, isLoading: isUpdating } = useBranchUpdateMutation({ + onError: () => { + toast.error(`Failed to update the branch`) + }, + }) + + const handleMarkBranchForReview = (branch: Branch) => { + if (branch.id && projectRef) { + updateBranch( + { + id: branch.id, + projectRef, + requestReview: true, + }, + { + onSuccess: () => { + toast.success('Merge request created') + router.push(`/project/${branch.project_ref}/merge`) + }, + } + ) + } + } + + const handleCloseMergeRequest = (branch: Branch) => { + if (branch.id && projectRef) { + updateBranch( + { + id: branch.id, + projectRef, + requestReview: false, + }, + { + onSuccess: () => { + toast.success('Merge request closed') + }, + } + ) + } + } + + const generateCreatePullRequestURL = (branch?: string) => { + if (githubConnection === undefined) return 'https://github.com' + + return branch !== undefined + ? `https://github.com/${githubConnection.repository.name}/compare/${mainBranch?.git_branch}...${branch}` + : `https://github.com/${githubConnection.repository.name}/compare` + } + + return ( + + +
+
+ {!canReadBranches ? ( + + ) : ( + <> + {isErrorConnections && ( + + )} + + {isErrorBranches && ( + + )} + + {!isError && ( +
+ {gitlessBranching && ( + <> + {isBranch && !isCurrentBranchReadyForReview && currentBranch && ( +
+
+
+ + {currentBranch.name} + last viewed +
+ +
+
+ )} + + )} + + {isLoadingBranches && ( +
+ +
+ )} + {mergeRequestBranches.length > 0 ? ( + mergeRequestBranches.map((branch) => { + const isPR = branch.pr_number !== undefined + const rowLink = isPR + ? `https://github.com/${repo}/pull/${branch.pr_number}` + : `/project/${branch.project_ref}/merge` + return ( + + {branch.name} + +
+ {branch.pr_number ? ( +

#{branch.pr_number}

+ ) : ( + <> + +

{mainBranch.name}

+ + )} +
+
+ } + repo={repo} + branch={branch} + rowLink={rowLink} + external={isPR} + rowActions={ + !isPR && ( + + +
+ )} + + )} +
+
+ + + ) +} + +const MergeRequestsPageWrapper = ({ children }: PropsWithChildren<{}>) => { + const router = useRouter() + const { ref } = useParams() + const project = useSelectedProject() + const gitlessBranching = useFlag('gitlessBranching') + + const isBranch = project?.parent_project_ref !== undefined + const projectRef = + project !== undefined ? (isBranch ? project.parent_project_ref : ref) : undefined + + const { data: branches } = useBranchesQuery({ projectRef }) + const previewBranches = (branches || []).filter((b) => !b.is_default) + + const { mutate: updateBranch, isLoading: isUpdating } = useBranchUpdateMutation({ + onError: () => { + toast.error(`Failed to update the branch`) + }, + }) + + const handleMarkBranchForReview = (branch: Branch) => { + if (branch.id && projectRef) { + updateBranch( + { + id: branch.id, + projectRef, + requestReview: true, + }, + { + onSuccess: () => { + toast.success('Merge request created') + router.push(`/project/${branch.project_ref}/merge`) + }, + } + ) + } + } + + const primaryActions = gitlessBranching ? ( + + ) : null + + const secondaryActions = ( + + ) + + return ( + + {children} + + ) +} + +MergeRequestsPage.getLayout = (page) => { + return ( + + + {page} + + + ) +} + +export default MergeRequestsPage diff --git a/apps/studio/pages/project/[ref]/merge.tsx b/apps/studio/pages/project/[ref]/merge.tsx index ccd69fe17df..699b5d44024 100644 --- a/apps/studio/pages/project/[ref]/merge.tsx +++ b/apps/studio/pages/project/[ref]/merge.tsx @@ -1,61 +1,60 @@ -import { useState, useEffect, useMemo, useCallback } from 'react' -import { useRouter } from 'next/router' import dayjs from 'dayjs' +import { + AlertTriangle, + ExternalLink, + GitBranchIcon, + GitMerge, + MoreVertical, + Shield, + X, +} from 'lucide-react' +import Link from 'next/link' +import { useRouter } from 'next/router' +import { useCallback, useEffect, useMemo, useState } from 'react' +import { toast } from 'sonner' import { useParams } from 'common' -import { ProjectLayoutWithAuth } from 'components/layouts/ProjectLayout/ProjectLayout' -import DefaultLayout from 'components/layouts/DefaultLayout' -import { PageLayout } from 'components/layouts/PageLayout/PageLayout' -import { useProjectByRef, useSelectedProject } from 'hooks/misc/useSelectedProject' -import { useBranchesQuery } from 'data/branches/branches-query' -import { useBranchMergeMutation } from 'data/branches/branch-merge-mutation' -import { useBranchPushMutation } from 'data/branches/branch-push-mutation' -import { useBranchDeleteMutation } from 'data/branches/branch-delete-mutation' -import { useBranchUpdateMutation } from 'data/branches/branch-update-mutation' -import { useBranchMergeDiff } from 'hooks/branches/useBranchMergeDiff' -import { useWorkflowManagement } from 'hooks/branches/useWorkflowManagement' import DatabaseDiffPanel from 'components/interfaces/BranchManagement/DatabaseDiffPanel' import EdgeFunctionsDiffPanel from 'components/interfaces/BranchManagement/EdgeFunctionsDiffPanel' import { OutOfDateNotice } from 'components/interfaces/BranchManagement/OutOfDateNotice' +import { ReviewWithAI } from 'components/interfaces/BranchManagement/ReviewWithAI' +import WorkflowLogsCard from 'components/interfaces/BranchManagement/WorkflowLogsCard' +import DefaultLayout from 'components/layouts/DefaultLayout' +import { PageLayout } from 'components/layouts/PageLayout/PageLayout' +import { ProjectLayoutWithAuth } from 'components/layouts/ProjectLayout/ProjectLayout' +import { ScaffoldContainer } from 'components/layouts/Scaffold' +import ProductEmptyState from 'components/to-be-cleaned/ProductEmptyState' +import { ButtonTooltip } from 'components/ui/ButtonTooltip' +import { useBranchDeleteMutation } from 'data/branches/branch-delete-mutation' +import { useBranchMergeMutation } from 'data/branches/branch-merge-mutation' +import { useBranchPushMutation } from 'data/branches/branch-push-mutation' +import { useBranchUpdateMutation } from 'data/branches/branch-update-mutation' +import { useBranchesQuery } from 'data/branches/branches-query' +import { useBranchMergeDiff } from 'hooks/branches/useBranchMergeDiff' +import { useWorkflowManagement } from 'hooks/branches/useWorkflowManagement' +import { useProjectByRef, useSelectedProject } from 'hooks/misc/useSelectedProject' +import { useFlag } from 'hooks/ui/useFlag' +import type { NextPageWithLayout } from 'types' import { Badge, Button, + cn, + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, NavMenu, NavMenuItem, - cn, - DropdownMenuTrigger, - DropdownMenuItem, - DropdownMenuContent, - DropdownMenu, } from 'ui' import ConfirmationModal from 'ui-patterns/Dialogs/ConfirmationModal' -import { toast } from 'sonner' -import type { NextPageWithLayout } from 'types' - -import { ScaffoldContainer } from 'components/layouts/Scaffold' -import { - GitBranchIcon, - GitMerge, - Shield, - ExternalLink, - AlertTriangle, - X, - MoreVertical, -} from 'lucide-react' -import Link from 'next/link' -import { ButtonTooltip } from 'components/ui/ButtonTooltip' -import WorkflowLogsCard from 'components/interfaces/BranchManagement/WorkflowLogsCard' -import ProductEmptyState from 'components/to-be-cleaned/ProductEmptyState' -import { useFlag } from 'hooks/ui/useFlag' -import { ReviewWithAI } from 'components/interfaces/BranchManagement/ReviewWithAI' const MergePage: NextPageWithLayout = () => { const router = useRouter() const { ref } = useParams() + const project = useSelectedProject() const gitlessBranching = useFlag('gitlessBranching') - const project = useSelectedProject() const [isSubmitting, setIsSubmitting] = useState(false) const [workflowFinalStatus, setWorkflowFinalStatus] = useState(null) const [showConfirmDialog, setShowConfirmDialog] = useState(false) @@ -103,10 +102,7 @@ const MergePage: NextPageWithLayout = () => { currentBranchCreatedAt: currentBranch?.created_at, }) - const { mutate: updateBranch } = useBranchUpdateMutation({ - onSuccess: () => { - toast.success('Branch updated successfully') - }, + const { mutate: updateBranch, isLoading: isUpdating } = useBranchUpdateMutation({ onError: (error) => { toast.error(`Failed to update branch: ${error.message}`) }, @@ -116,10 +112,6 @@ const MergePage: NextPageWithLayout = () => { const currentWorkflowRunId = router.query.workflow_run_id as string | undefined - useEffect(() => { - setWorkflowFinalStatus(null) - }, [currentWorkflowRunId]) - const handleCurrentBranchWorkflowComplete = useCallback( (status: string) => { setWorkflowFinalStatus(status) @@ -135,11 +127,16 @@ const MergePage: NextPageWithLayout = () => { refetchDiff() clearDiffsOptimistically() if (parentProjectRef && currentBranch?.id && currentBranch.review_requested_at) { - updateBranch({ - id: currentBranch.id, - projectRef: parentProjectRef, - requestReview: false, - }) + updateBranch( + { + id: currentBranch.id, + projectRef: parentProjectRef, + requestReview: false, + }, + { + onSuccess: () => toast.success('Branch updated successfully'), + } + ) } }, [ @@ -267,31 +264,23 @@ const MergePage: NextPageWithLayout = () => { const handleReadyForReview = () => { if (!currentBranch?.id || !parentProjectRef) return - updateBranch({ - id: currentBranch.id, - projectRef: parentProjectRef, - requestReview: true, - }) - } - - const handleShowConfirmDialog = () => { - setShowConfirmDialog(true) - } - - const handleConfirmMerge = () => { - setShowConfirmDialog(false) - handleMerge() - } - - const handleCancelMerge = () => { - setShowConfirmDialog(false) + updateBranch( + { + id: currentBranch.id, + projectRef: parentProjectRef, + requestReview: true, + }, + { + onSuccess: () => toast.success('Successfully marked as ready for review'), + } + ) } const breadcrumbs = useMemo( () => [ { - label: 'Branches', - href: `/project/${project?.ref}/branches?tab=prs`, + label: 'Merge requests', + href: `/project/${project?.ref}/branches/merge-requests`, }, ], [parentProjectRef] @@ -321,6 +310,10 @@ const MergePage: NextPageWithLayout = () => { ] }, [currentWorkflowRunId, currentTab]) + useEffect(() => { + setWorkflowFinalStatus(null) + }, [currentWorkflowRunId]) + if (!gitlessBranching) { return ( @@ -387,7 +380,7 @@ const MergePage: NextPageWithLayout = () => { disabled={!combinedHasChanges || isCombinedDiffLoading} icon={} > - Ready for review + Mark as ready for review ) : isMergeDisabled ? ( { type="primary" loading={isMerging || isSubmitting} disabled={isMergeDisabled} - onClick={handleShowConfirmDialog} + onClick={() => setShowConfirmDialog(true)} icon={} > Merge branch @@ -412,7 +405,7 @@ const MergePage: NextPageWithLayout = () => {
@@ -455,9 +396,8 @@ export default function JWTSecretKeysTable() { diff --git a/apps/studio/components/interfaces/JwtSecrets/jwt-secret-keys-table/signing-key-row.tsx b/apps/studio/components/interfaces/JwtSecrets/jwt-secret-keys-table/signing-key-row.tsx index 6e6075b0532..4459f71e1f2 100644 --- a/apps/studio/components/interfaces/JwtSecrets/jwt-secret-keys-table/signing-key-row.tsx +++ b/apps/studio/components/interfaces/JwtSecrets/jwt-secret-keys-table/signing-key-row.tsx @@ -2,12 +2,12 @@ import { motion } from 'framer-motion' import { CircleArrowDown, CircleArrowUp, - Trash2, Eye, Key, MoreVertical, ShieldOff, Timer, + Trash2, } from 'lucide-react' import { components } from 'api-types' @@ -28,8 +28,8 @@ import { statusColors, statusLabels } from '../jwt.constants' interface SigningKeyRowProps { signingKey: components['schemas']['SigningKeyResponse'] - setSelectedKey: (key: JWTSigningKey | null) => void - setShownDialog: (dialog: 'key-details' | 'revoke' | 'delete' | null) => void + setSelectedKey: (key?: JWTSigningKey) => void + setShownDialog: (dialog?: 'key-details' | 'revoke' | 'delete') => void handlePreviouslyUsedKey: (keyId: string) => void handleStandbyKey: (keyId: string) => void legacyKey?: JWTSigningKey | null diff --git a/apps/studio/components/interfaces/JwtSecrets/jwt-settings.tsx b/apps/studio/components/interfaces/JwtSecrets/jwt-settings.tsx index 8ef4fe5bc16..49c06672fc3 100644 --- a/apps/studio/components/interfaces/JwtSecrets/jwt-settings.tsx +++ b/apps/studio/components/interfaces/JwtSecrets/jwt-settings.tsx @@ -4,43 +4,40 @@ import { JwtSecretUpdateProgress, JwtSecretUpdateStatus, } from '@supabase/shared-types/out/events' -import { - AlertCircle, - ChevronDown, - Eye, - EyeOff, - Hourglass, - Key, - CloudOff, - Loader2, - PenTool, - Power, - RefreshCw, - TriangleAlert, - ExternalLink, - Lightbulb, -} from 'lucide-react' -import { Dispatch, SetStateAction, useState } from 'react' -import { toast } from 'sonner' -import { number, object } from 'yup' -import Link from 'next/link' import { useParams } from 'common' import { ButtonTooltip } from 'components/ui/ButtonTooltip' import { FormActions } from 'components/ui/Forms/FormActions' import Panel from 'components/ui/Panel' +import { useLegacyAPIKeysStatusQuery } from 'data/api-keys/legacy-api-keys-status-query' import { useAuthConfigQuery } from 'data/auth/auth-config-query' import { useAuthConfigUpdateMutation } from 'data/auth/auth-config-update-mutation' import { useJwtSecretUpdateMutation } from 'data/config/jwt-secret-update-mutation' import { useJwtSecretUpdatingStatusQuery } from 'data/config/jwt-secret-updating-status-query' import { useProjectPostgrestConfigQuery } from 'data/config/project-postgrest-config-query' import { useLegacyJWTSigningKeyQuery } from 'data/jwt-signing-keys/legacy-jwt-signing-key-query' -import { useLegacyAPIKeysStatusQuery } from 'data/api-keys/legacy-api-keys-status-query' import { useCheckPermissions } from 'hooks/misc/useCheckPermissions' +import { useFlag } from 'hooks/ui/useFlag' import { uuidv4 } from 'lib/helpers' import { - AlertDescription_Shadcn_, - AlertTitle_Shadcn_, - Alert_Shadcn_, + AlertCircle, + ChevronDown, + CloudOff, + ExternalLink, + Eye, + EyeOff, + Hourglass, + Key, + Lightbulb, + Loader2, + PenTool, + Power, + RefreshCw, + TriangleAlert, +} from 'lucide-react' +import Link from 'next/link' +import { Dispatch, SetStateAction, useState } from 'react' +import { toast } from 'sonner' +import { Button, DropdownMenu, DropdownMenuContent, @@ -51,12 +48,10 @@ import { Input, InputNumber, Modal, - WarningIcon, } from 'ui' import { Admonition } from 'ui-patterns/admonition' -import ConfirmationModal from 'ui-patterns/Dialogs/ConfirmationModal' import TextConfirmModal from 'ui-patterns/Dialogs/TextConfirmModal' -import { useFlag } from 'hooks/ui/useFlag' +import { number, object } from 'yup' import { JWT_SECRET_UPDATE_ERROR_MESSAGES, JWT_SECRET_UPDATE_PROGRESS_MESSAGES, @@ -193,25 +188,29 @@ const JWTSettings = () => { ) : ( <> {legacyKey && legacyKey.status !== 'revoked' && ( - - You've successfully migrated your legacy JWT secret to the new JWT Signing - Keys feature. Changing the legacy JWT secret now can only be done by - rotating to a standby key and finally revoking it. Now used to{' '} - - {legacyKey.status === 'in_use' ? 'sign and verify' : 'only verify'} - {' '} - JSON Web Tokens by Supabase products.{' '} + +

+ Changing the legacy JWT secret can only be done by rotating to a standby + key and then revoking it. It is used to{' '} + + {legacyKey.status === 'in_use' ? 'sign and verify' : 'only verify'} + {' '} + JSON Web Tokens by Supabase products. +

+ {legacyAPIKeysStatus && legacyAPIKeysStatus.enabled && ( - <> +

This includes the anon and service_role JWT based API keys. {' '} Consider switching to publishable and secret API keys to disable them. - +

)} -
-
+ From 985b530382d40a647c1fd43f6d422ec48ca73677 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kevin=20Gr=C3=BCneberg?= Date: Mon, 14 Jul 2025 16:31:35 +0800 Subject: [PATCH 06/93] chore: hint for compute costs in upgrade screen (#37094) --- .../SubscriptionPlanUpdateDialog.tsx | 46 +++++++++++-------- 1 file changed, 27 insertions(+), 19 deletions(-) diff --git a/apps/studio/components/interfaces/Organization/BillingSettings/Subscription/SubscriptionPlanUpdateDialog.tsx b/apps/studio/components/interfaces/Organization/BillingSettings/Subscription/SubscriptionPlanUpdateDialog.tsx index e1b473bf68e..4b180b48862 100644 --- a/apps/studio/components/interfaces/Organization/BillingSettings/Subscription/SubscriptionPlanUpdateDialog.tsx +++ b/apps/studio/components/interfaces/Organization/BillingSettings/Subscription/SubscriptionPlanUpdateDialog.tsx @@ -1,7 +1,6 @@ import { Check, InfoIcon } from 'lucide-react' import Link from 'next/link' import { useMemo, useRef, useState } from 'react' -import tweets from 'shared-data/tweets' import { toast } from 'sonner' import { @@ -33,12 +32,6 @@ import { plans as subscriptionsPlans } from 'shared-data/plans' const stripePromise = loadStripe(STRIPE_PUBLIC_KEY) -const getRandomTweet = () => { - const filteredTweets = tweets.filter((it) => it.text.length < 180) - const randomIndex = Math.floor(Math.random() * filteredTweets.length) - return filteredTweets[randomIndex] -} - const PLAN_HEADINGS = { tier_pro: 'the Pro plan to unlock unlimited projects, daily backups, and email support whenever you need it', @@ -101,8 +94,6 @@ export const SubscriptionPlanUpdateDialog = ({ } as StripeElementsOptions }, [paymentIntentSecret, resolvedTheme]) - const testimonialTweet = useMemo(() => getRandomTweet(), []) - const changeType = useMemo(() => { return getPlanChangeType(subscription?.plan?.id, planMeta?.id) }, [planMeta, subscription]) @@ -340,8 +331,8 @@ export const SubscriptionPlanUpdateDialog = ({

Your new monthly invoice

- Each paid project runs on a dedicated 24/7 server. First project uses - Compute Credits; additional ones cost $10+ + Paid projects run 24/7 without pausing. First project uses Compute + Credits; additional projects start at $10 /month regardless of usage.{' '} )} + {projects.filter( + (it) => + it.status === PROJECT_STATUS.ACTIVE_HEALTHY || + it.status === PROJECT_STATUS.COMING_UP + ).length === 1 && + subscriptionPlanMeta?.planId === 'pro' && + changeType === 'upgrade' && ( +

+ +
+ Paid projects run 24/7 without pausing. First project uses Compute Credits; + additional projects cost $10+ + /month regardless of usage.{' '} +
+ + Learn more + +
+
+ )} +
)} - {changeType !== 'downgrade' && ( -
-
- {testimonialTweet.text} -
— @{testimonialTweet.handle}
-
-
- )}
From 30466832101eb3a505dc7bcd73f1fa9e571ce570 Mon Sep 17 00:00:00 2001 From: Stojan Dimitrovski Date: Mon, 14 Jul 2025 10:33:41 +0200 Subject: [PATCH 07/93] docs: jwt signing keys (#37027) * docs: jwt signing keys * apply suggestion from @charislam Co-authored-by: Charis <26616127+charislam@users.noreply.github.com> * add cryptography to spellchecker * fix word Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> * remove easily Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> * fix significantly --------- Co-authored-by: Charis <26616127+charislam@users.noreply.github.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- .../NavigationMenu.constants.ts | 8 +- apps/docs/content/guides/auth/jwt-fields.mdx | 10 +- apps/docs/content/guides/auth/jwts.mdx | 317 +++++++++++------- .../docs/content/guides/auth/signing-keys.mdx | 189 +++++++++++ .../guides/auth-signing-keys/states.mermaid | 9 + .../img/guides/auth-signing-keys/states.svg | 2 + supa-mdx-lint/Rule003Spelling.toml | 5 + 7 files changed, 407 insertions(+), 133 deletions(-) create mode 100644 apps/docs/content/guides/auth/signing-keys.mdx create mode 100644 apps/docs/public/img/guides/auth-signing-keys/states.mermaid create mode 100644 apps/docs/public/img/guides/auth-signing-keys/states.svg diff --git a/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts b/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts index 367dc2d10b4..2d3df44c8cf 100644 --- a/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts +++ b/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts @@ -718,8 +718,12 @@ export const auth = { { name: 'Password Security', url: '/guides/auth/password-security' }, { name: 'Rate Limits', url: '/guides/auth/rate-limits' }, { name: 'Bot Detection (CAPTCHA)', url: '/guides/auth/auth-captcha' }, - { name: 'JWTs', url: '/guides/auth/jwts' }, - { name: 'JWT Fields Reference', url: '/guides/auth/jwt-fields' }, + { + name: 'JSON Web Tokens (JWT)', + url: '/guides/auth/jwts', + items: [{ name: 'Claims Reference', url: '/guides/auth/jwt-fields' }], + }, + { name: 'JWT Signing Keys', url: '/guides/auth/signing-keys' }, { name: 'Row Level Security', url: '/guides/database/postgres/row-level-security' }, { name: 'Column Level Security', diff --git a/apps/docs/content/guides/auth/jwt-fields.mdx b/apps/docs/content/guides/auth/jwt-fields.mdx index 2a154286456..8ee9029fe6e 100644 --- a/apps/docs/content/guides/auth/jwt-fields.mdx +++ b/apps/docs/content/guides/auth/jwt-fields.mdx @@ -1,10 +1,10 @@ --- id: 'jwt-fields' -title: 'JWT Fields Reference' -subtitle: 'Complete reference for JWT fields in Supabase' +title: 'JWT Claims Reference' +subtitle: 'Complete reference for claims appearing in JWTs created by Supabase Auth' --- -This page provides a comprehensive reference for all JWT fields used in Supabase authentication tokens. This information is essential for server-side JWT validation and serialization, especially when implementing authentication in languages like Rust where field names like `ref` are reserved keywords. +This page provides a comprehensive reference for all JWT claims used in Supabase authentication tokens. This information is essential for server-side JWT validation and serialization, especially when implementing authentication in languages like Rust where field names like `ref` are reserved keywords. ## JWT structure overview @@ -46,7 +46,7 @@ These claims may be present depending on the authentication context: | `user_metadata` | `object` | **User Metadata** - User-specific data | `{"name": "John Doe"}` | | `amr` | `array` | **Authentication Methods Reference** - List of authentication methods used | `[{"method": "password", "timestamp": 1640991600}]` | -## Special fields +## Special claims | Field | Type | Description | Example | Context | | ----- | -------- | --------------------------------------------------- | ------------------------ | ----------------------------- | @@ -167,7 +167,7 @@ struct JwtClaims { role: String, iat: i64, exp: i64, - // ... other fields + // ... other claims } ``` diff --git a/apps/docs/content/guides/auth/jwts.mdx b/apps/docs/content/guides/auth/jwts.mdx index 0e68d9952f4..a20b58a6cb1 100644 --- a/apps/docs/content/guides/auth/jwts.mdx +++ b/apps/docs/content/guides/auth/jwts.mdx @@ -1,180 +1,245 @@ --- id: 'auth-jwts' -title: 'JWTs' -subtitle: 'JSON Web Tokens' -tocVideo: 'v3Exg5YpJvE' +title: 'JSON Web Token (JWT)' +subtitle: 'Information on how best to use JSON Web Tokens with Supabase' --- A [JSON Web Token](https://jwt.io/introduction) is a type of data structure, represented as a string, that usually contains identity and authorization information about a user. It encodes information about its lifetime and is signed with a cryptographic key to make it tamper-resistant. -Supabase Access Tokens are JWTs. The JWT is sent along with every request to Supabase services. By verifying the token and inspecting the included claims, you can allow or deny access to resources. [Row Level Security](/docs/guides/database/postgres/row-level-security) policies are based on the information present in JWTs. +Supabase Auth continuously issues a new JWT for each user session, for as long as the user remains signed in. Check the comprehensive guide on [Sessions](/docs/guides/sessions) to find out how you can tailor this process for your needs. -## Encoding and signing JWTs +JWTs provide the foundation for [Row Level Security](/docs/guides/database/row-level-security). Each Supabase product is able to securely decode and verify the validity of a JWT it receives before using Postgres policies and roles to authorize access to the project's data. -JWTs are encoded and signed as follows. +Supabase provides a comprehensive system of managing [JWT Signing Keys](/docs/guides/auth/signing-keys) used to create and verify JSON Web Tokens. -The JSON object starts out looking something like this: +## Introduction -```js +JWTs are strings that have the following structure: + +``` +
.. +``` + +Each part is a string of [Base64-URL](https://en.wikipedia.org/wiki/Base64#Variants_summary_table) encoded JSON, or bytes for the signature. + +**Header** + +```json { - "sub": "0001", - "name": "Sam Vimes", - "iat": 1516239022, - "exp": 1518239022 + "typ": "JWT", + "alg": "", + "kid": "" } ``` -`sub` is the "subject", which is usually the UUID of the user. `name` is self-explanatory, and `iat` is the Unix timestamp at which the token was created. Many JWTs will also have an `exp`, which is the date at which the token is set to expire and can no longer be used. These are some of the standard fields you may find in a JWT, but you can pretty much store whatever you want in there, for example: +Gives some basic identifying information about the string, indicating its type `typ`, the cryptographic algorithm `alg` that can be used to verify the data, and optionally the unique key identifier that should be used when verifying it. -```js +**Payload** + +```json { - "sub": "0002", - "name": "Věra Hrabánková", - "iat": 1516239022, - "exp": 1518239022, - "theme": { - "primary" : "#D80C14", - "secondary" : "#FFFFFF" - } + "iss": "https://project_id.supabase.co/auth/v1", + "exp": 12345678, + "sub": "", + "role": "authenticated", + "email": "someone@example.com", + "phone": "+15552368" + // ... } ``` -Just note that the more data you store in your token, the longer the encoded string will be. +Provides identifying information (called "claims") about the user (or other entity) that is represented by the token. Usually a JWT conveys information about what the user can access (then called Access Token) or who the user is (then called ID Token). You can use a [Custom Access Token Hook](/docs/guides/auth/auth-hooks/custom-access-token-hook) to add, remove or change claims present in the token. A few claims are important: -When we want to send the JWT to the user, we first encode the data using an algorithm such as `HS256`. There are many libraries (and several different algorithms) that can be used to do this encoding/decoding, such as [`jsonwebtoken`](https://www.npmjs.com/package/jsonwebtoken). The signing is as simple as: +| Claim | Description | +| -------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `iss` | Identifies the server which issued the token. If you append `/.well-known/jwks.json` to this URL you'll get access to the public keys with which you can verify the token. | +| `exp` | Sets a time limit after which the token should not be trusted and is considered expired, even if it is properly signed. | +| `sub` | Means _subject_, is the unique ID of the user represented by the token. | +| `role` | The Postgres role to use when applying Row Level Security policies. | +| ... | All other claims are useful for quick access to profile information without having to query the database or send a request to the Auth server. | -```js -// from https://replit.com/@awalias/jsonwebtokens#index.js -let token = jwt.sign({ name: 'Sam Vimes' }, 'some-secret') -``` +**Signature** -And the resulting string will look like this: +A [digital signature](https://en.wikipedia.org/wiki/Digital_signature) using a [shared secret](https://en.wikipedia.org/wiki/HMAC) or [public-key cryptography](https://en.wikipedia.org/wiki/Public-key_cryptography). The purpose of the signature is to verify the authenticity of the `
.` string without relying on database access, liveness or performance of the Auth server. To verify the signature avoid implementing the algorithms yourself and instead rely on `supabase.auth.getClaims()`, or other high-quality JWT verification libraries for your language. -```js -eyJhbGciOiJIUzI1NiJ9 - .eyJzdWIiOiIwMDAxIiwibmFtZSI6IlNhbSBWaW1lcyIsImlhdCI6MTUxNjIzOTAyMiwiZXhwIjoxNTE4MjM5MDIyfQ - .zMcHjKlkGhuVsiPIkyAkB2rjXzyzJsMMgpvEGvGtjvA -``` +## Supabase and JWTs -You will notice that the string is actually made up of three components: +Supabase creates JWTs in these cases for you: -The first segment `eyJhbGciOiJIUzI1NiJ9` is known as the "header", and when decoded just tells us which algorithm was used to do the encoding: +1. When using Supabase Auth, an access token (JWT) is created for each user while they remain signed in. These are short lived, so they are continuously issued as your user interacts with Supabase APIs. +2. As the legacy JWT-based [API keys](/docs/guides/api/api-keys) `anon` and `service_role`. These have a 10 year expiry and are signed with a shared secret, making them hard to rotate or expire. These JWTs express public access via the `anon` key, or elevated access via the `service_role` key. We strongly recommend switching to publishable and secret API keys. +3. On-the-fly when using publishable or secret API keys. Each API key is transformed into a short-lived JWT that is then used to authorize access to your data. Accessing these short-lived tokens is generally not possible. -```js -{ - "alg": "HS256" -} -``` +In addition to creating JWTs, Supabase can also accept JWTs from other Auth servers via the [Third-Party Auth](/docs/guides/auth/third-party/overview) feature or ones you've made yourself using the legacy JWT secret or if you've imported in [JWT Signing Key](/docs/guides/auth/signing-keys). -The second segment eyJzdWIiOiIwMDAxIiwibmFtZSI6IlNhbSBWaW1lcyIsImlhdCI6MTUxNjIzOTAyMiwiZXhwIjoxNTE4MjM5MDIyfQ contains our original payload: +## Using custom or third-party JWTs -```js -{ - "sub": "0001", - "name": "Sam Vimes", - "iat": 1516239022, - "exp": 1518239022 -} -``` + -The last segment `zMcHjKlkGhuVsiPIkyAkB2rjXzyzJsMMgpvEGvGtjvA` is the signature itself, which is the part used by the website or service provider to verify that a token sent by some user is legitimate. It is produced in the first instance by running the cryptographic function HS256 on the following input: +The `supabase.auth.getClaims()` method is meant to be used only with JWTs issued by Supabase Auth. If you make your own JWTs using the legacy JWT secret or a key you've imported, the verification may fail. We strongly recommend using a JWT verification library for your language to verify this type of JWT based on the claims you're adding in them. -```js -HMACSHA256( - base64UrlEncode(header) + "." + - base64UrlEncode(payload) - -) -``` + -You can test out minting your own tokens on [https://jwt.io](https://jwt.io). +Your Supabase project accepts a JWT in the `Authorization: Bearer ` header. If you're using the Supabase client library, it does this for you. -It is important to note that anyone who possesses the `jwt_secret` here can create new tokens, and also verify existing ones. More advanced JWT algorithms use two secrets: one for the creation of tokens, and a separate one to verify the validity of signed tokens. +If you are already using Supabase Auth, when a user is signed in, their access token JWT is automatically managed and sent for you with every API call. -You might wonder why JWTs are so popular all of a sudden. The answer is that with the mass adoption of microservice architecture, we were in a situation where several distinct microservices (APIs, websites, servers, etc.) want to validate that a user is who they say they are, or are in other words a "logged-in" user. Traditional session tokens are no use here, since they would require each microservice to either maintain a record of currently valid session tokens or to query a central database each time a user wants to access a resource in order to check the validity of the session token – very inefficient indeed. JWT-based auth in this sense is decentralized, since anyone with the `jwt_secret` can verify a token without needing access to a centralized database. +If you wish to send a JWT from a Third-Party Auth provider, or one you made yourself by using the legacy JWT secret or a JWT signing key you imported, you can pass it to the client library using the `accessToken` option. -{/* supa-mdx-lint-disable-next-line Rule004ExcludeWords */} + -Note: One downside of JWTs is that they are not easily voidable, unlike session tokens. If a JWT is leaked to a malicious actor, they will be able to redeem it anywhere until the expiry date is reached – unless of course the system owner updates the `jwt_secret` (which will of course invalidate _everyone's_ existing tokens). + -## JWTs in Supabase +```typescript +import { createClient } from '@supabase/supabase-js' -In Supabase we issue JWTs for three different purposes: - -1. `anon key`: This key is used to bypass the Supabase API gateway and can be used in your client-side code. -2. `service role key`: This key has super admin rights and can bypass your Row Level Security. Do not put it in your client-side code. Keep it private. -3. `user specific jwts`: These are tokens we issue to users who log into your project/service/website. It's the modern equivalent of a session token, and can be used by a user to access content or permissions specific to them. - -The first token here, the `anon key` token, is for developers to send along with their API requests whenever they want to interact with their Supabase database. - -Let's say you want to read the names of all the rows in a table `colors`. We would make a request like: - -```bash -curl 'https://xscduanzzfseqszwzhcy.supabase.co/rest/v1/colors?select=name' \ --H "apikey: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiYW5vbiIsImlhdCI6MTYxNDIwNTE3NCwiZXhwIjoxOTI5NzgxMTc0fQ.-NBR1WnZyQGpRLdXJfgfpszoZ0EeE6KHatJsDPLIX8c" -``` - -If we put this token into https://jwt.io, we see it decodes to: - -```js -{ - "role": "anon", - "iss": "supabase", - "iat": 1614205174, - "exp": 1929781174 -} -``` - -This JWT is signed by a `jwt_secret` specific to the developer's Supabase token (you can find this secret alongside this encoded "anon key" on your Dashboard under Settings > API page) and is required to get past the Supabase API gateway and access the developer's project. - -The idea with this particular key is that it's safe to put into your client, meaning it's okay if your end users see this key – but _only_ if you first enable Row Level Security. - -The second key, `service role key`, should only ever be used on one of your own servers or environments, and should never be shared with end users. You might use this token to do things like make batch inserts of data. - -The `user access token` is the JWT issued when you call for example: - -```js -supabase.auth.signIn({ - email: 'valid.email@supabase.io', - password: 'They_Live_1988!', +const supabase = createClient('https://.supabase.co', 'SUPABASE_ANON_KEY', { + accessToken: async () => { + return '' + }, }) ``` -This token should be passed in addition to the `apikey` header as an `Authorization Bearer` header like: + -```bash -curl 'https://xscduanzzfseqszwzhcy.supabase.co/rest/v1/colors?select=name' \ --H "apikey: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiYW5vbiIsImlhdCI6MTYxNDIwNTE3NCwiZXhwIjoxOTI5NzgxMTc0fQ.-NBR1WnZyQGpRLdXJfgfpszoZ0EeE6KHatJsDPLIX8c" \ --H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJhdXRoZW50aWNhdGVkIiwiZXhwIjoxNjE1ODI0Mzg4LCJzdWIiOiIwMzM0NzQ0YS1mMmEyLTRhYmEtOGM4YS02ZTc0OGY2MmExNzIiLCJlbWFpbCI6InNvbWVvbmVAZW1haWwuY29tIiwiYXBwX21ldGFkYXRhIjp7InByb3ZpZGVyIjoiZW1haWwifSwidXNlcl9tZXRhZGF0YSI6bnVsbCwicm9sZSI6ImF1dGhlbnRpY2F0ZWQifQ.I-_oSsJamtinGxniPETBf-ezAUwDW2sY9bJIThvdX9s" + + +```dart +await Supabase.initialize( + url: supabaseUrl, + anonKey: supabaseKey, + debug: false, + accessToken: () async { + return ""; + }, +); ``` -You'll notice that this token is quite a bit longer, since it contains information specific to the user such as: + -```js -{ - "aud": "authenticated", - "exp": 1615824388, - "sub": "0334744a-f2a2-4aba-8c8a-6e748f62a172", - "email": "valid.email@supabase.io", - "app_metadata": { - "provider": "email" - }, - "user_metadata": null, - "role": "authenticated" + + +```swift +import Supabase + +let supabase = SupabaseClient( + supabaseURL: URL(string: "https://.supabase.co")!, + supabaseKey: "SUPABASE_ANON_KEY", + options: SupabaseClientOptions( + auth: SupabaseClientOptions.AuthOptions( + accessToken: { + return "" + } + ) + ) +) +``` + + + + + +```kotlin +val supabase = createSupabaseClient( + "https://.supabase.co", + "SUPABASE_ANON_KEY" +) { + accessToken = { + "" + } } ``` -If using the service role key, you'll need to pass it into both the `apikey` and `authorization` headers (again, only do this from a secure environment such as your own server): + -```bash -curl "$YOUR_PROJECT_URL/rest/v1/colors?select=name" \ - -H "apikey: $YOUR_SERVICE_ROLE_KEY" \ - -H "authorization: Bearer $YOUR_SERVICE_ROLE_KEY" + + +In the past there was a recommendation to set custom headers on the Supabase client with the `Authorization` header including your custom JWT. This is no longer recommended as it's less flexible and causes confusion when combined with a user session from Supabase Auth. + +## Verifying a JWT from Supabase + +If you're not able to use the Supabase client libraries, the following can be used to help you securely verify JWTs issued by Supabase. + +Supabase Auth exposes a [JSON Web Key](https://datatracker.ietf.org/doc/html/rfc7517) Set URL for each Supabase project: + +```http +GET https://project-id.supabase.co/auth/v1/.well-known/jwks.json ``` -Now that you understand what JWTs are and where they're used in Supabase, you can explore how to use them in combination with Row Level Security to start restricting access to certain tables, rows, and columns in your Postgres database. +Which responds with JWKS object containing one or more asymmetric [JWT signing keys](/docs/guides/auth/signing-keys) (only their public keys). + +```json +{ + "keys": [ + { + "kid": "", + "alg": "", + "kty": "", + "key_ops": ["verify"] + // public key fields + } + ] +} +``` + +This endpoint is served directly from the Auth server, but is also additionally cached by the Supabase Edge for 10 minutes, significantly speeding up access to this data regardless of where you're performing the verification. It's important to be aware of the cache expiry time to prevent unintentionally rejecting valid user access tokens. We recommend waiting at least 20 minutes when creating a standby signing key, or revoking a previously used key. + +Make sure that you do not cache this data for longer in your application, as it might make revocation difficult. If you do, make sure to provide a way to purge this cache when rotating signing keys to avoid unintentionally rejecting valid user access tokens. + +Below is an example of how to use the [jose TypeScript JWT verification library](https://github.com/panva/jose) with Supabase JWTs: + +```typescript +import { jwtVerify, createRemoteJWKSet } from 'jose' + +const PROJECT_JWKS = createRemoteJWKSet( + new URL('https://project-id.supabase.co/auth/v1/.well-known/jwks.json') +) + +/** + * Verifies the provided JWT against the project's JSON Web Key Set. + */ +async function verifyProjectJWT(jwt: string) { + return jwtVerify(jwt, PROJECT_JWKS) +} +``` + +### Verifying with the legacy JWT secret or a shared secret signing key + +If your project is still using the legacy JWT secret, or you're using a shared secret (HS256) signing key, we recommend always verifying a user access token directly with the Auth server by sending a request like so: + +```http +GET https://project-id.supabase.co/auth/v1/user +apikey: publishable or anon legacy API key +Authorization: Bearer +``` + +If the server responds with HTTP 200 OK, the JWT is valid, otherwise it is not. + +Because the Auth server runs only in your project's specified region and is not globally distributed, doing this check can be quite slow depending on where you're performing the check. Avoid doing checks like this from servers or functions running on the edge, and prefer routing to a server within the same geographical region as your project. + +If you are using the legacy JWT secret, or you've imported your own shared secret (HS256) signing key, you may wish to verify using the shared secret. **We strongly recommend against this approach.** + + + +There is almost no benefit from using a JWT signed with a shared secret. Although it's computationally more efficient and verification is simpler to code by hand, using this approach can expose your project's data to significant security vulnerabilities or weaknesses. + +Consider the following: + +- Using a shared secret can make it more difficult to keep aligned with security compliance frameworks such as SOC2, PCI-DSS, ISO27000, HIPAA, etc. +- A shared secret that is in the hands of a malicious actor can be used to impersonate your users, give them access to privileged actions or data. +- It is difficult to detect or identify when or how a shared secret has been given to a malicious actor. +- Consider who might have even accidental access to the shared secret: systems, staff, devices (and their disk encryption and vulnerability patch status). +- A malicious actor can use a shared secret **far into the future**, so lacking current evidence of compromise does not mean your data is secure. +- It can be very easy to accidentally leak the shared secret in publicly available source code such as in your website or frontend, mobile app package or other executable. This is especially true if you accidentally add the secret in environment variables prefixed with `NEXT_PUBLIC_`, `VITE_`, `PUBLIC_` or other conventions by web frameworks. +- Rotating shared secrets might require careful coordination to avoid downtime of your app. + + + +Check the JWT verification libraries for your language on how to securely verify JWTs signed with the legacy JWT secret or a shared secret (HS256) signing key. We strongly recommend relying on the Auth server as described above, or switching to a different signing key based on public key cryptography (RSA, Elliptic Curves) instead. ## Resources - JWT debugger: https://jwt.io/ -- [JWT Fields Reference](/docs/guides/auth/jwt-fields) - Complete reference for all JWT fields in Supabase +- [JWT Signing Keys](/docs/guides/auth/signing-keys) +- [JWT Claims Reference](/docs/guides/auth/jwt-fields) - Complete reference for all JWT claims used by Supabase Auth +- [API keys](/docs/guides/api/api-keys) diff --git a/apps/docs/content/guides/auth/signing-keys.mdx b/apps/docs/content/guides/auth/signing-keys.mdx new file mode 100644 index 00000000000..1810a3861f7 --- /dev/null +++ b/apps/docs/content/guides/auth/signing-keys.mdx @@ -0,0 +1,189 @@ +--- +id: 'auth-signing-keys' +title: 'JWT Signing Keys' +subtitle: 'Best practices on managing keys used by Supabase Auth to create and verify JSON Web Tokens' +--- + +Supabase Auth continuously issues a new JWT for each user session, for as long as the user remains signed in. JWT signing keys provide fine grained control over this important process for the security of your application. + +Before continuing check the comprehensive guide on [Sessions](/docs/guides/auth/sessions) for all the details about how Auth creates tokens for a user's session. Read up on [JWTs](/docs/guides/auth/jwts) if you are not familiar with the basics. + +## Overview + +When a JWT is issued by Supabase Auth, the key used to create its [signature](https://en.wikipedia.org/wiki/Digital_signature) is known as the signing key. Supabase provides two systems for dealing with signing keys: the Legacy system based on the JWT secret, and the new Signing keys system. + +| System | Type | Description | +| ----------------- | ------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Legacy | JWT secret | Initially Supabase was designed to use a single shared secret key to sign all JWTs. This includes the `anon` and `service_role` keys, all user access tokens including some [Storage pre-signed URLs](/docs/reference/javascript/storage-from-createsignedurl). **No longer recommended.** Available for backward compatibility. | +| Signing keys | Asymmetric key (RSA, Elliptic Curves) | A JWT signing key based on [public-key cryptography](https://en.wikipedia.org/wiki/Public-key_cryptography) (RSA, Elliptic Curves) that follows industry best practices and significantly improves the security, reliability and performance of your applications. | +| Signing keys | Shared secret key | A JWT signing key based on a [shared secret](https://en.wikipedia.org/wiki/HMAC). | + +### Benefits of the signing keys system + +We've designed the Signing keys system to address many problems the legacy system had. It goes hand-in-hand with the [publishable and secret API keys](/docs/guides/api/api-keys). + +| Benefit | Legacy JWT secret | JWT signing keys | +| ------------------------------------------- | ---------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Performance | Increased app latency as JWT validation is done by Auth server. | If using asymmetric signing key, JWT validation is fast and does not involve Auth server. | +| Reliability | To ensure secure revocation, Auth server is in the hot path of your application. | If using asymmetric signing key, JWT validation is local and fast and does not involve Auth server. | +| Security | Requires changing of your application's backend components to fully revoke a compromised secret. | If using asymmetric signing key, revocation is automatic via the key discovery endpoint. | +| Zero-downtime rotation | Downtime, sometimes being significant. Requires careful coordination with [API keys](/docs/guides/api/api-keys). | No downtime, as each rotation step is independent and reversible. | +| Users signed out during rotation | Currently active users get immediately signed out. | No users get signed out. | +| Independence from API keys | `anon` and `service_role` must be rotated simultaneously. | [Publishable and secret API keys](/docs/guides/api/api-keys) no longer are based on the JWT signing key and can be independently managed. | +| Security compliance frameworks (SOC2, etc.) | Difficult to remain aligned as the secret can be extracted from Supabase. | Easier alignment as the private key or shared secret can't be extracted. [Row Level Security](/docs/guides/database/postgres/row-level-security) has strong key revocation guarantees. | + +## Getting started + +You can start migrating away from the legacy JWT secret through the Supabase dashboard. This process does not cause downtime for your application. + +1. Start off by clicking the _Migrate JWT secret_ button on the [JWT signing keys](/dashboard/project/_/settings/jwt/signing-keys) page. This step will import the existing legacy JWT secret into the new JWT signing keys system. Once this process completes, you will no longer be able to rotate the legacy JWT secret using the old system. +2. Simultaneously, we're creating a new asymmetric JWT signing key for you to rotate to. This key starts off as standby key -- meaning it's being advertised as a key that Supabase Auth will use in the future to create JWTs. +3. If you're not ready to switch away from the legacy JWT secret right now, you can stop here without any issue. If you wish to use a different signing key -- either to use a different signing algorithm (RSA, Elliptic Curve or shared secret) or to import a private key or shared secret you already have -- feel free to move the standby key to _Previously used_ before finally moving it to _Revoked._ +4. If you do wish to start using the standby key for all new JWT use the _Rotate keys_ button. A few important notes: + - Make sure your app does not directly rely on the legacy JWT secret. If it's verifying every JWT against the legacy JWT secret (using a library like `jose`, `jsonwebtoken` or similar), continuing with the rotation might break those components. + - If you're using [Edge Functions](/docs/guides/functions) that have the Verify JWT setting, continuing with the rotation might break your app. You will need to turn off this setting. + - In both cases, change or add code to your app or Edge Function that verifies the JWT. Use the `supabase.auth.getClaims()` function or read more about [Verifying a JWT from Supabase](/docs/guides/auth/jwts#verifying-a-jwt-from-supabase) on the best way to do this. +5. Rotating the keys immediately causes the Auth server to issue new JWT access tokens for signed in users signed with the new key. Non-expired access tokens will remain to be accepted, so no users will be forcefully signed out. +6. Plan for revocation of the legacy JWT secret. + - If your access token expiry time is configured to be 1 hour, wait at least 1 hour and 15 minutes before revoking the legacy JWT secret -- now under the _Previously used_ section. + - This prevents currently active users from being forcefully signed out. + - In some situations, such as an active security incident you may want to revoke the legacy JWT secret immediately. + +## Rotating and revoking keys + +Key rotation and revocation are one of the most important processes for maintaining the security of your project and applications. The signing keys system allows you to efficiently execute these without causing downtime of your app, a deficiency present in the legacy system. Below are some common reasons when and why you should consider key rotation and revocation. + +**Malicious actors abusing the legacy JWT secret, or imported private key** + +- The legacy JWT secret has been leaked in logs, committed to source control, or accidentally exposed in the frontend build of your application, a library, desktop or mobile app package, etc. +- You suspect that a [member of your organization](/docs/guides/platform/access-control) has lost control of their devices, and a malicious actor may have accessed the JWT secret via the Supabase dashboard or by accessing your application's backend configuration. +- You suspect that an ex-team-member of your organization may be a malicious actor, by abusing the power the legacy JWT secret provides. +- Make sure you also switch to [publishable and secret API keys](/docs/guides/api/api-keys) and disable the `anon` and `service_role` keys. +- If you've imported a private key, and you're suspecting that this private key has been compromised on your end similarly. + +**Closer alignment to security best practices and compliance frameworks (SOC2, PCI-DSS, ISO27000, HIPAA, ...)** + +- It is always prudent to rotate signing keys at least once a year. +- Some security compliance frameworks strongly encourage or require frequent cryptographic key rotation. +- If you're using Supabase as part of a large enterprise, this may be required by your organization's security department. +- Creating muscle memory for the time you'll need to respond to an active security incident. + +**Changing key algorithm for technical reasons** + +- You may wish to switch signing algorithms due to compatibility problems or to simplify development on your end. + +### Lifetime of a signing key + +
+ +Diagram showing the state transitions of a signing key + +
+ +A newly created key starts off as standby, before being rotated into in use (becoming the current key) while the existing current key becomes previously used. + +At any point you can move a key from the previously used or revoked states back to being a standby key, and rotate to it. This gives you the confidence to revert back to an older key if you identify problems with the rotation, such as forgetting to update a component of your application that is relying on a specific key (for example, the legacy JWT secret). + +Each action on a key is reversible (except permanent deletion). + +
+ +
+ +| Action | Accepted JWT signatures | Description | +| -------------------------------------------------------------------------------- | ---------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Create a new key | Current key only, new key has not created any JWTs yet. | When you initially create a key, after choosing the signing algorithm or importing a private key you already have, it starts out in the standby state. If using an asymmetric key (RSA, Elliptic Curve) its public key will be available in the discovery endpoint. Supabase Auth does not use this key to create new JWTs. | +| Rotate keys | Both keys in the rotation. | Rotation only changes the key used by Supabase Auth to create new JWTs, but the trust relationship with both keys remains. | +| Revoke key | Only from the current key. | Once all regularly valid JWTs have expired (or sooner) revoke the previously used key to revoke trust in it. | +| Move to standby from revoked | Current and previously revoked key. | If you've made a mistake or need more time to adjust your application, you can move a revoked key to standby. Follow up with a rotation to ensure Auth starts using the originally revoked key again to make new JWTs. | +| Move to standby from previously used | Both keys. | This only prepares the key from the last rotation to be used by Auth to make new JWTs with it. | +| Delete key | - | Permanently destroys the private key or shared secret of a key, so it will not be possible to re-use or rotate again into it. | + +### Public key discovery and caching + +When your signing keys use an asymmetric algorithm based on [public-key cryptography](https://en.wikipedia.org/wiki/Public-key_cryptography) Supabase Auth exposes the public key in the JSON Web Key Set discovery endpoint, for anyone to see. This is an important security feature allowing you to rotate and revoke keys without needing to deploy new versions of your app's backend infrastructure. + +Access the currently trusted signing keys at the following endpoint: + +```http +GET https://project-id.supabase.co/auth/v1/.well-known/jwks.json +``` + +Note that this is secure as public keys are irreversible and can only be used to verify the signature of JSON Web Tokens, but not create new ones. + +This discovery endpoint is cached by Supabase's edge servers for 10 minutes. Furthermore the Supabase client libraries may cache the keys in memory for an additional 10 minutes. Your application may be using different caching behavior if you're not relying only on the Supabase client library. + +This multi-level cache is a trade-off allowing fast JWT verification without placing the Auth server in the hot path of your application, increasing its reliability and performance. + +Importantly Supabase products **do not rely on this cache**, so stronger security guarantees are provided especially when keys are revoked. If your application only uses [Row Level Security](/docs/guides/database/postgres/row-level-security) policies and does not have any other backend components (such as APIs, Edge Functions, servers, etc.) key rotation and revocation are instantaneous. + +Finally this multi-level cache is cleared every 20 minutes, or longer if you have a custom setup. Consider the following problems that may arise due to it: + +- **Urgent key revocation.** If you are in a security incident where a signing key must be urgently revoked, due to the multi-level cache your application components may still trust and authenticate JWTs signed with the revoked key. Supabase products (Auth, Data API, Storage, Realtime) **do not rely on this cache and revocation is instantaneous.** Should this be an issue for you, ensure you've built a cache busting mechanism as part of your app's backend infrastructure. +- **Quick key creation and rotation.** If you're migrating away from the legacy JWT secret or when only using the `supabase.auth.getClaims()` method this case is handled for you automatically. If you're verifying JWTs on your own, without the help of the Supabase client library, ensure that **all caches in your app** have picked up the newly created standby key before proceeding to rotation. + +## Choosing the right signing algorithm + +To strike the right balance between performance, security and ease-of-use, JWT signing keys are based on capabilities available in the [Web Crypto API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Crypto_API). + +| Algorithm | JWT `alg` | Information | +| ------------------------------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| [NIST P-256 Curve](https://en.wikipedia.org/wiki/Elliptic-curve_cryptography)
(Asymmetric) | `ES256` | Elliptic Curves are a faster alternative than RSA, while providing comparable security. Especially important for Auth use cases is the fact that signatures using the P-256 curve are significantly shorter than those created by RSA, which reduces data transfer sizes and helps in managing cookie size. Web Crypto and most other cryptography libraries and runtimes support this curve. | +| [RSA 2048](https://en.wikipedia.org/wiki/RSA_cryptosystem)
(Asymmetric) | `RS256` | RSA is the oldest and most widely supported public-key cryptosystem in use. While being easy to code by hand, it can be significantly slower than elliptic curves in certain aspects. We recommend using the P-256 elliptic curve instead. | +| [Ed25519 Curve](https://en.wikipedia.org/wiki/EdDSA#Ed25519)
(Asymmetric) | `EdDSA` | Coming soon. This algorithm is based on a different elliptic curve cryptosystem developed in the open, unlike the P-256 curve. Web Crypto or other crypto libraries may not support it in all runtimes, making it difficult to work with. | +| [HMAC with shared secret](https://en.wikipedia.org/wiki/HMAC)
(Symmetric) | `HS256` | **Not recommended for production applications.** A shared secret uses a message authentication code to verify the authenticity of a JSON Web Token. This requires that both the creator of the JWT (Auth) and the system verifying the JWT know the secret. As there is no public key counterpart, revoking this key might require deploying changes to your app's backend infrastructure. | + + + +There is almost no benefit from using a JWT signed with a shared secret. Although it's computationally more efficient and verification is simpler to code by hand, using this approach can expose your project's data to significant security vulnerabilities or weaknesses. + +Consider the following: + +- Using a shared secret can make it more difficult to keep aligned with security compliance frameworks such as SOC2, PCI-DSS, ISO27000, HIPAA, etc. +- A shared secret that is in the hands of a malicious actor can be used to impersonate your users, give them access to privileged actions or data. +- It is difficult to detect or identify when or how a shared secret has been given to a malicious actor. +- Consider who might have even accidental access to the shared secret: systems, staff, devices (and their disk encryption and vulnerability patch status). +- A malicious actor can use a shared secret **far into the future**, so lacking current evidence of compromise does not mean your data is secure. +- It can be very easy to accidentally leak the shared secret in publicly available source code such as in your website or frontend, mobile app package or other executable. This is especially true if you accidentally add the secret in environment variables prefixed with `NEXT_PUBLIC_`, `VITE_`, `PUBLIC_` or other conventions by web frameworks. +- Rotating shared secrets might require careful coordination to avoid downtime of your app. + + + +## Frequently asked questions + +### Why is it not possible to extract the private key or shared secret from Supabase? + +You can only extract the legacy JWT secret. Once you've moved to using the JWT signing keys feature extracting of the private key or shared secret from Supabase is not possible. This ensures that no one in your organization is able to impersonate your users or gain privileged access to your project's data. + +This guarantee provides your application with close alignment with security compliance frameworks (SOC2, PCI-DSS, ISO27000, HIPAA) and security best practices. + +### How to create (mint) JWTs if access to the private key or shared secret is not possible? + +If you wish to make your own JWTs or have access to the private key or shared secret used by Supabase, you can create a new JWT signing key by importing a private key or setting a shared secret yourself. + +Use the [Supabase CLI](/docs/reference/cli/introduction) to quickly and securely generate a private key ready for import: + +```sh +supabase gen generate-key ES256 +``` + +Make sure you store this private key in a secure location, as it will not be extractable from Supabase. + +### Why is a 5 minute wait imposed when changing signing key states? + +Changing a JWT signing key's state sets off many changes inside the Supabase platform. To ensure a consistent setup, most actions that change the state of a JWT signing key are throttled for approximately 5 minutes. + +### Why is deleting the legacy JWT secret disallowed? + +This is to ensure you have the ability, should you need it, to go back to the legacy JWT secret. In the future this capability will be allowed from the dashboard. + +### Why does revoking the legacy JWT secret require disabling of `anon` and `service_role` API keys? + +Unfortunately `anon` and `service_role` are not just API keys, but are also valid JSON Web Tokens, signed by the legacy JWT secret. Revoking the legacy JWT secret means that your application no longer trusts any JWT signed with it. Therefore before you revoke the legacy JWT secret, you must disable the `anon` and `service_role` to ensure a consistent security setup. diff --git a/apps/docs/public/img/guides/auth-signing-keys/states.mermaid b/apps/docs/public/img/guides/auth-signing-keys/states.mermaid new file mode 100644 index 00000000000..4422a737677 --- /dev/null +++ b/apps/docs/public/img/guides/auth-signing-keys/states.mermaid @@ -0,0 +1,9 @@ +stateDiagram-v2 + [*] --> standby : A new key is created and advertized + standby --> in_use : Once all components have picked up the new key, new JWTs can be issued with it + in_use --> previously_used : Rotation, JWT remain accepted + previously_used --> revoked : Once all JWTs created with the previous key expire (or sooner) + revoked --> [*] : Delete permanently after 7 days + + revoked --> standby + previously_used --> standby diff --git a/apps/docs/public/img/guides/auth-signing-keys/states.svg b/apps/docs/public/img/guides/auth-signing-keys/states.svg new file mode 100644 index 00000000000..fc49db026d7 --- /dev/null +++ b/apps/docs/public/img/guides/auth-signing-keys/states.svg @@ -0,0 +1,2 @@ + +

A new key is created and advertized

Once all components have picked up the new key, new JWTs can be issued with it

Rotation, JWT remain accepted

Once all JWTs created with the previous key expire (or sooner)

Delete permanently after 7 days

standby

in_use

previously_used

revoked

diff --git a/supa-mdx-lint/Rule003Spelling.toml b/supa-mdx-lint/Rule003Spelling.toml index 09b956065c2..c9848e923f8 100644 --- a/supa-mdx-lint/Rule003Spelling.toml +++ b/supa-mdx-lint/Rule003Spelling.toml @@ -37,6 +37,9 @@ allow_list = [ "[Cc]ooldowns?", "[Cc]oroutines?", "[Cc]ron", + "[Cc]rypto", + "[Cc]ryptography", + "[Cc]ryptosystem", "[Dd]atasets?", "[Dd]atasources?", "[Dd]e facto", @@ -48,6 +51,7 @@ allow_list = [ "[Ee]ntrypoints?", "[Ee]nums?", "[Ee]nv", + "[Ee]x", "[Ee]xecutables?", "[Ff]atals", "[Ff]rontend", @@ -59,6 +63,7 @@ allow_list = [ "[Hh]ypertables?", "[KMG]bps", "[KMG]iB", + "[Ll]iveness", "[Mm]atryoshka", "[Mm]essageBird", "[Mm]icroservices?", From 4b0bd733128440cd9264f445d7e714efa528f08e Mon Sep 17 00:00:00 2001 From: Jonathan Summers-Muir Date: Mon, 14 Jul 2025 19:40:17 +0800 Subject: [PATCH 08/93] chore: jwt keys page table layout (#37097) * Improve layout and truncation in SigningKeyRow Added flex and truncation classes to ensure status labels and key IDs are properly truncated and aligned. This enhances the table's appearance and prevents overflow issues for long text. * Improve icon layout and badge styling in UI components Added flex-shrink-0 to icons in AlgorithmHoverCard for better alignment. Updated InfoPill to use min-w-0, overflow-hidden, and improved badge and label layout for consistent appearance and handling of long content. * Remove unused cn import from InfoPill component The cn utility import was removed from InfoPill.tsx as it is no longer used. The Badge component now uses a direct className string instead. * Update JWT key table columns and add rotation info Removed the 'Key ID' column and added a 'Last rotated at' column to the JWT secret keys table. The signing key row now displays the relative time since the key was last updated for previously used keys. * Add Key ID column to JWT secret keys table Introduces a new 'Key ID' column to the JWTSecretKeysTable component for improved visibility and management of JWT secret keys. * Improve JWT key table UI and add tooltip to key ID Updated the JWT secret keys table to enhance the empty state with an icon and explanatory text, and adjusted column alignment and visibility for 'Last rotated at'. Added a tooltip to the key ID for better accessibility and ensured the 'Last rotated at' column displays for both previously used and revoked keys. * Improve API key table layout and responsiveness API key name and description are now grouped together, with description shown under the name. The last seen column is hidden on smaller screens and displays 'Never used' when appropriate. ApiKeyPill max width is now responsive to screen size. The description column was removed from SecretAPIKeys to streamline the table. * Adjust API key pill and input sizing for responsiveness Reduced max-widths for ApiKeyPill and updated PublishableAPIKeys layout to improve responsiveness. ApiKeyInput now uses dynamic min/max widths for better display across breakpoints. * Adjust lg breakpoint min-width for API key input Changed the lg:min-w value from 40rem to 24rem for the API key input field to improve layout responsiveness at large screen sizes. * Update PublishableAPIKeys.tsx * Remove unused file * Minor refactors --------- Co-authored-by: Joshen Lim --- .../interfaces/APIKeys/APIKeyRow.tsx | 21 ++++++++++++---- .../interfaces/APIKeys/APIKeysTable.tsx | 25 ------------------- .../interfaces/APIKeys/ApiKeyPill.tsx | 6 ++--- .../interfaces/APIKeys/PublishableAPIKeys.tsx | 11 +++++--- .../interfaces/APIKeys/SecretAPIKeys.tsx | 6 ++--- .../JwtSecrets/algorithm-hover-card.tsx | 7 +++--- .../jwt-secret-keys-table/index.tsx | 16 ++++++++++-- .../jwt-secret-keys-table/signing-key-row.tsx | 20 ++++++++++----- apps/studio/components/ui/InfoPill.tsx | 23 +++++++++-------- 9 files changed, 71 insertions(+), 64 deletions(-) delete mode 100644 apps/studio/components/interfaces/APIKeys/APIKeysTable.tsx diff --git a/apps/studio/components/interfaces/APIKeys/APIKeyRow.tsx b/apps/studio/components/interfaces/APIKeys/APIKeyRow.tsx index 2800dcda2d8..1a0eaa02a9e 100644 --- a/apps/studio/components/interfaces/APIKeys/APIKeyRow.tsx +++ b/apps/studio/components/interfaces/APIKeys/APIKeyRow.tsx @@ -1,5 +1,6 @@ -import { MoreVertical } from 'lucide-react' +import { APIKeysData } from 'data/api-keys/api-keys-query' import { motion } from 'framer-motion' +import { MoreVertical } from 'lucide-react' import { Button, DropdownMenu, @@ -10,7 +11,6 @@ import { } from 'ui' import { APIKeyDeleteDialog } from './APIKeyDeleteDialog' import { ApiKeyPill } from './ApiKeyPill' -import { APIKeysData } from 'data/api-keys/api-keys-query' export const APIKeyRow = ({ apiKey, @@ -34,14 +34,25 @@ export const APIKeyRow = ({ mass: 1, }} > - {apiKey.name} + +
+ {apiKey.name} +
+ {apiKey.description || No description} +
+
+
- {apiKey.description || '/'} - {lastSeen?.timestamp ?? '/'} + + +
+ {lastSeen?.timestamp ?? Never used} +
+
diff --git a/apps/studio/components/interfaces/APIKeys/APIKeysTable.tsx b/apps/studio/components/interfaces/APIKeys/APIKeysTable.tsx deleted file mode 100644 index 49aa3a94a97..00000000000 --- a/apps/studio/components/interfaces/APIKeys/APIKeysTable.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import { AnimatePresence } from 'framer-motion' - -import { APIKeysData } from 'data/api-keys/api-keys-query' -import { APIKeyRow } from './APIKeyRow' - -const APIKeysTable = ({ - apiKeys, -}: { - apiKeys: Extract[] -}) => { - return ( - - - - - {apiKeys.map((apiKey) => ( - - ))} - - -
- ) -} - -export default APIKeysTable diff --git a/apps/studio/components/interfaces/APIKeys/ApiKeyPill.tsx b/apps/studio/components/interfaces/APIKeys/ApiKeyPill.tsx index 6024d017720..28d24040b1d 100644 --- a/apps/studio/components/interfaces/APIKeys/ApiKeyPill.tsx +++ b/apps/studio/components/interfaces/APIKeys/ApiKeyPill.tsx @@ -1,10 +1,10 @@ +import { PermissionAction } from '@supabase/shared-types/out/constants' import { useQueryClient } from '@tanstack/react-query' import { AnimatePresence, motion } from 'framer-motion' import { Eye } from 'lucide-react' import { useEffect, useState } from 'react' import { toast } from 'sonner' -import { PermissionAction } from '@supabase/shared-types/out/constants' import { InputVariants } from '@ui/components/shadcn/ui/input' import { useParams } from 'common' import CopyButton from 'components/ui/CopyButton' @@ -35,7 +35,6 @@ export function ApiKeyPill({ // It fetches the fully revealed API key when needed const { data, - isLoading: isLoadingApiKey, error, refetch: refetchApiKey, } = useAPIKeyIdQuery( @@ -106,7 +105,6 @@ export function ApiKeyPill({ // States for disabling buttons/showing tooltips const isRestricted = isSecret && !canManageSecretKeys - const isLoading = isLoadingPermission return ( <> @@ -118,7 +116,7 @@ export function ApiKeyPill({ isSecret ? 'overflow-hidden' : '', show ? 'ring-1 ring-foreground-lighter ring-opacity-50' : 'ring-0 ring-opacity-0', 'transition-all', - 'max-w-[340px]', + 'max-w-[100px] sm:max-w-[140px] md:max-w-[180px] lg:max-w-[340px]', 'cursor-text', 'relative' )} diff --git a/apps/studio/components/interfaces/APIKeys/PublishableAPIKeys.tsx b/apps/studio/components/interfaces/APIKeys/PublishableAPIKeys.tsx index 99627770315..179df09b662 100644 --- a/apps/studio/components/interfaces/APIKeys/PublishableAPIKeys.tsx +++ b/apps/studio/components/interfaces/APIKeys/PublishableAPIKeys.tsx @@ -1,12 +1,14 @@ import { PermissionAction } from '@supabase/shared-types/out/constants' +import { useMemo } from 'react' + import { InputVariants } from '@ui/components/shadcn/ui/input' import { useParams } from 'common' import CopyButton from 'components/ui/CopyButton' import { FormHeader } from 'components/ui/Forms/FormHeader' import { useAPIKeysQuery } from 'data/api-keys/api-keys-query' import { useCheckPermissions, usePermissionsLoaded } from 'hooks/misc/useCheckPermissions' -import { useMemo } from 'react' import { cn, EyeOffIcon, Input_Shadcn_, Skeleton, WarningIcon } from 'ui' + // to add in later with follow up PR // import CreatePublishableAPIKeyDialog from './CreatePublishableAPIKeyDialog' // to add in later with follow up PR @@ -38,7 +40,7 @@ export const PublishableAPIKeys = () => { description="This key is safe to use in a browser if you have enabled Row Level Security (RLS) for your tables and configured policies." />
-
+
Publishable key
@@ -59,7 +61,7 @@ export const PublishableAPIKeys = () => {
) : (
- Publishable key can be safely shared in public + The publishable key can be safely shared publicly
)}
@@ -91,7 +93,8 @@ function ApiKeyInput() { // The default publisahble key will always be the first one const apiKey = publishableApiKeys[0] - const baseClasses = 'flex-1 grow gap-1 rounded-full min-w-[32em]' + const baseClasses = + 'flex-1 grow gap-1 rounded-full min-w-0 max-w-[200px] sm:max-w-[300px] md:max-w-[400px] lg:min-w-[24rem]' const size = 'tiny' if (isApiKeysLoading || isPermissionsLoading) { diff --git a/apps/studio/components/interfaces/APIKeys/SecretAPIKeys.tsx b/apps/studio/components/interfaces/APIKeys/SecretAPIKeys.tsx index 16a6a60e9fb..5f6c6e1b83f 100644 --- a/apps/studio/components/interfaces/APIKeys/SecretAPIKeys.tsx +++ b/apps/studio/components/interfaces/APIKeys/SecretAPIKeys.tsx @@ -112,10 +112,8 @@ export const SecretAPIKeys = () => { API Key - - Description - - + + Last Seen diff --git a/apps/studio/components/interfaces/JwtSecrets/algorithm-hover-card.tsx b/apps/studio/components/interfaces/JwtSecrets/algorithm-hover-card.tsx index e546c0900e3..1d69c0051a0 100644 --- a/apps/studio/components/interfaces/JwtSecrets/algorithm-hover-card.tsx +++ b/apps/studio/components/interfaces/JwtSecrets/algorithm-hover-card.tsx @@ -1,5 +1,4 @@ import { LockKeyholeOpen, RectangleEllipsis } from 'lucide-react' -import React from 'react' import { InfoPill } from 'components/ui/InfoPill' import { AlgorithmDetail, algorithmDetails } from './algorithm-details' @@ -9,7 +8,7 @@ interface AlgorithmHoverCardProps { legacy?: boolean } -export const AlgorithmHoverCard: React.FC = ({ algorithm, legacy }) => { +export const AlgorithmHoverCard = ({ algorithm, legacy }: AlgorithmHoverCardProps) => { const details: AlgorithmDetail = algorithmDetails[algorithm] return ( @@ -17,9 +16,9 @@ export const AlgorithmHoverCard: React.FC = ({ algorith label={{legacy ? `Legacy ${details.label}` : details.label}} icon={ algorithm === 'HS256' ? ( - + ) : ( - + ) } title={details.name} diff --git a/apps/studio/components/interfaces/JwtSecrets/jwt-secret-keys-table/index.tsx b/apps/studio/components/interfaces/JwtSecrets/jwt-secret-keys-table/index.tsx index 5b0389385c3..51eb792891c 100644 --- a/apps/studio/components/interfaces/JwtSecrets/jwt-secret-keys-table/index.tsx +++ b/apps/studio/components/interfaces/JwtSecrets/jwt-secret-keys-table/index.tsx @@ -257,6 +257,9 @@ export default function JWTSecretKeysTable() { Type + + Last rotated at + Actions @@ -280,8 +283,14 @@ export default function JWTSecretKeysTable() { ) : ( -
- No previously used keys +
+ +
+

No previously used keys

+

+ Rotated keys will appear here for verification of existing tokens +

+
)} @@ -312,6 +321,9 @@ export default function JWTSecretKeysTable() { Type + + Last rotated at + Actions diff --git a/apps/studio/components/interfaces/JwtSecrets/jwt-secret-keys-table/signing-key-row.tsx b/apps/studio/components/interfaces/JwtSecrets/jwt-secret-keys-table/signing-key-row.tsx index 4459f71e1f2..15ea69469ce 100644 --- a/apps/studio/components/interfaces/JwtSecrets/jwt-secret-keys-table/signing-key-row.tsx +++ b/apps/studio/components/interfaces/JwtSecrets/jwt-secret-keys-table/signing-key-row.tsx @@ -1,3 +1,4 @@ +import dayjs from 'dayjs' import { motion } from 'framer-motion' import { CircleArrowDown, @@ -73,24 +74,26 @@ export const SigningKeyRow = ({ )} > {signingKey.status === 'standby' ? ( - + ) : ( - + )} - {statusLabels[signingKey.status]} + {statusLabels[signingKey.status]}
-
+
- {signingKey.id} + + {signingKey.id} +
@@ -100,6 +103,11 @@ export const SigningKeyRow = ({ legacy={signingKey.id === legacyKey?.id} /> + {(signingKey.status === 'previously_used' || signingKey.status === 'revoked') && ( + + {dayjs(signingKey.updated_at).fromNow()} + + )} {(signingKey.status !== 'in_use' || signingKey.algorithm !== 'HS256') && ( diff --git a/apps/studio/components/ui/InfoPill.tsx b/apps/studio/components/ui/InfoPill.tsx index a1287d2a693..d88beae7ebf 100644 --- a/apps/studio/components/ui/InfoPill.tsx +++ b/apps/studio/components/ui/InfoPill.tsx @@ -1,11 +1,12 @@ -import React from 'react' +import { ExternalLink } from 'lucide-react' +import React, { ReactNode } from 'react' + +import { Badge } from 'ui/src/components/shadcn/ui/badge' import { HoverCard, HoverCardContent, HoverCardTrigger, } from 'ui/src/components/shadcn/ui/hover-card' -import { Badge } from 'ui/src/components/shadcn/ui/badge' -import { ExternalLink } from 'lucide-react' interface Link { url: string @@ -13,19 +14,21 @@ interface Link { } interface InfoPillProps { - label: string | React.ReactNode - icon: React.ReactNode + label: string | ReactNode + icon: ReactNode title: string - description: string | React.ReactNode + description: string | ReactNode links?: Link[] } -export const InfoPill: React.FC = ({ label, icon, title, description, links }) => { +export const InfoPill = ({ label, icon, title, description, links }: InfoPillProps) => { return ( - - - {icon} {label} + + + + {icon} {label} + From ef5c15a652848f3c85d996678cf5632b9fad410a Mon Sep 17 00:00:00 2001 From: Saxon Fletcher Date: Mon, 14 Jul 2025 22:04:57 +1000 Subject: [PATCH 09/93] Add platform kit to library (#37068) * add platform kit to library * controls * fix lockfile * add missing deps * dynamic form * update dialog * prettier * drawer * update ai route --------- Co-authored-by: Alaister Young --- apps/ui-library/__registry__/index.tsx | 12 + .../ui-library/components/side-navigation.tsx | 10 +- apps/ui-library/config/docs.ts | 12 + .../content/docs/platform/platform-kit.mdx | 110 + apps/ui-library/package.json | 36 +- apps/ui-library/public/llms.txt | 2 +- .../public/r/platform-kit-nextjs.json | 209 + apps/ui-library/registry.json | 177 + .../registry/default/components/ui/alert.tsx | 52 + .../registry/default/components/ui/badge.tsx | 33 + .../registry/default/components/ui/chart.tsx | 326 + .../default/components/ui/command.tsx | 161 + .../registry/default/components/ui/dialog.tsx | 129 + .../registry/default/components/ui/drawer.tsx | 124 + .../registry/default/components/ui/form.tsx | 169 + .../default/components/ui/hover-card.tsx | 29 + .../default/components/ui/popover.tsx | 42 + .../registry/default/components/ui/select.tsx | 153 + .../registry/default/components/ui/switch.tsx | 52 + .../registry/default/components/ui/table.tsx | 91 + .../default/components/ui/toggle-group.tsx | 57 + .../registry/default/components/ui/toggle.tsx | 43 + .../app/api/ai/sql/route.ts | 110 + .../app/api/supabase-proxy/[...path]/route.ts | 112 + .../components/dynamic-form.tsx | 417 + .../components/logo-supabase.tsx | 49 + .../components/results-table.tsx | 60 + .../components/sql-editor.tsx | 420 + .../components/supabase-manager/auth.tsx | 244 + .../components/supabase-manager/database.tsx | 330 + .../components/supabase-manager/index.tsx | 301 + .../components/supabase-manager/logs.tsx | 273 + .../components/supabase-manager/secrets.tsx | 195 + .../components/supabase-manager/storage.tsx | 77 + .../supabase-manager/suggestions.tsx | 121 + .../components/supabase-manager/users.tsx | 59 + .../components/users-growth-chart.tsx | 95 + .../contexts/SheetNavigationContext.tsx | 69 + .../platform-kit-nextjs/hooks/use-auth.ts | 68 + .../platform-kit-nextjs/hooks/use-logs.ts | 63 + .../hooks/use-run-query.ts | 44 + .../platform-kit-nextjs/hooks/use-secrets.ts | 110 + .../platform-kit-nextjs/hooks/use-storage.ts | 63 + .../hooks/use-suggestions.ts | 53 + .../platform-kit-nextjs/hooks/use-tables.ts | 23 + .../hooks/use-user-counts.ts | 47 + .../platform/platform-kit-nextjs/lib/logs.ts | 111 + .../lib/management-api-schema.d.ts | 7483 +++++++++++++++++ .../platform-kit-nextjs/lib/management-api.ts | 6 + .../platform-kit-nextjs/lib/pg-meta/index.ts | 59 + .../platform-kit-nextjs/lib/pg-meta/sql.ts | 225 + .../platform-kit-nextjs/lib/pg-meta/types.ts | 76 + .../platform-kit-nextjs/lib/schemas/auth.ts | 269 + .../lib/schemas/secrets.ts | 29 + .../platform-kit-nextjs/registry-item.json | 177 + apps/ui-library/registry/index.ts | 2 + apps/ui-library/registry/platform.ts | 5 + pnpm-lock.yaml | 449 +- 58 files changed, 14269 insertions(+), 54 deletions(-) create mode 100644 apps/ui-library/content/docs/platform/platform-kit.mdx create mode 100644 apps/ui-library/public/r/platform-kit-nextjs.json create mode 100644 apps/ui-library/registry/default/components/ui/alert.tsx create mode 100644 apps/ui-library/registry/default/components/ui/badge.tsx create mode 100644 apps/ui-library/registry/default/components/ui/chart.tsx create mode 100644 apps/ui-library/registry/default/components/ui/command.tsx create mode 100644 apps/ui-library/registry/default/components/ui/dialog.tsx create mode 100644 apps/ui-library/registry/default/components/ui/drawer.tsx create mode 100644 apps/ui-library/registry/default/components/ui/form.tsx create mode 100644 apps/ui-library/registry/default/components/ui/hover-card.tsx create mode 100644 apps/ui-library/registry/default/components/ui/popover.tsx create mode 100644 apps/ui-library/registry/default/components/ui/select.tsx create mode 100644 apps/ui-library/registry/default/components/ui/switch.tsx create mode 100644 apps/ui-library/registry/default/components/ui/table.tsx create mode 100644 apps/ui-library/registry/default/components/ui/toggle-group.tsx create mode 100644 apps/ui-library/registry/default/components/ui/toggle.tsx create mode 100644 apps/ui-library/registry/default/platform/platform-kit-nextjs/app/api/ai/sql/route.ts create mode 100644 apps/ui-library/registry/default/platform/platform-kit-nextjs/app/api/supabase-proxy/[...path]/route.ts create mode 100644 apps/ui-library/registry/default/platform/platform-kit-nextjs/components/dynamic-form.tsx create mode 100644 apps/ui-library/registry/default/platform/platform-kit-nextjs/components/logo-supabase.tsx create mode 100644 apps/ui-library/registry/default/platform/platform-kit-nextjs/components/results-table.tsx create mode 100644 apps/ui-library/registry/default/platform/platform-kit-nextjs/components/sql-editor.tsx create mode 100644 apps/ui-library/registry/default/platform/platform-kit-nextjs/components/supabase-manager/auth.tsx create mode 100644 apps/ui-library/registry/default/platform/platform-kit-nextjs/components/supabase-manager/database.tsx create mode 100644 apps/ui-library/registry/default/platform/platform-kit-nextjs/components/supabase-manager/index.tsx create mode 100644 apps/ui-library/registry/default/platform/platform-kit-nextjs/components/supabase-manager/logs.tsx create mode 100644 apps/ui-library/registry/default/platform/platform-kit-nextjs/components/supabase-manager/secrets.tsx create mode 100644 apps/ui-library/registry/default/platform/platform-kit-nextjs/components/supabase-manager/storage.tsx create mode 100644 apps/ui-library/registry/default/platform/platform-kit-nextjs/components/supabase-manager/suggestions.tsx create mode 100644 apps/ui-library/registry/default/platform/platform-kit-nextjs/components/supabase-manager/users.tsx create mode 100644 apps/ui-library/registry/default/platform/platform-kit-nextjs/components/users-growth-chart.tsx create mode 100644 apps/ui-library/registry/default/platform/platform-kit-nextjs/contexts/SheetNavigationContext.tsx create mode 100644 apps/ui-library/registry/default/platform/platform-kit-nextjs/hooks/use-auth.ts create mode 100644 apps/ui-library/registry/default/platform/platform-kit-nextjs/hooks/use-logs.ts create mode 100644 apps/ui-library/registry/default/platform/platform-kit-nextjs/hooks/use-run-query.ts create mode 100644 apps/ui-library/registry/default/platform/platform-kit-nextjs/hooks/use-secrets.ts create mode 100644 apps/ui-library/registry/default/platform/platform-kit-nextjs/hooks/use-storage.ts create mode 100644 apps/ui-library/registry/default/platform/platform-kit-nextjs/hooks/use-suggestions.ts create mode 100644 apps/ui-library/registry/default/platform/platform-kit-nextjs/hooks/use-tables.ts create mode 100644 apps/ui-library/registry/default/platform/platform-kit-nextjs/hooks/use-user-counts.ts create mode 100644 apps/ui-library/registry/default/platform/platform-kit-nextjs/lib/logs.ts create mode 100644 apps/ui-library/registry/default/platform/platform-kit-nextjs/lib/management-api-schema.d.ts create mode 100644 apps/ui-library/registry/default/platform/platform-kit-nextjs/lib/management-api.ts create mode 100644 apps/ui-library/registry/default/platform/platform-kit-nextjs/lib/pg-meta/index.ts create mode 100644 apps/ui-library/registry/default/platform/platform-kit-nextjs/lib/pg-meta/sql.ts create mode 100644 apps/ui-library/registry/default/platform/platform-kit-nextjs/lib/pg-meta/types.ts create mode 100644 apps/ui-library/registry/default/platform/platform-kit-nextjs/lib/schemas/auth.ts create mode 100644 apps/ui-library/registry/default/platform/platform-kit-nextjs/lib/schemas/secrets.ts create mode 100644 apps/ui-library/registry/default/platform/platform-kit-nextjs/registry-item.json create mode 100644 apps/ui-library/registry/platform.ts diff --git a/apps/ui-library/__registry__/index.tsx b/apps/ui-library/__registry__/index.tsx index 28f3f0c29f0..003c426d55e 100644 --- a/apps/ui-library/__registry__/index.tsx +++ b/apps/ui-library/__registry__/index.tsx @@ -403,6 +403,18 @@ export const Index: Record = { chunks: [] } , + "platform-kit-nextjs": { + name: "platform-kit-nextjs", + type: "registry:block", + registryDependencies: ["alert","badge","button","card","chart","command","dialog","drawer","form","hover-card","input","label","popover","select","skeleton","switch","table","toggle","toggle-group","tooltip"], + component: React.lazy(() => import("@/registry/default/platform/platform-kit-nextjs/components/dynamic-form.tsx")), + source: "", + files: ["registry/default/platform/platform-kit-nextjs/app/api/ai/sql/route.ts","registry/default/platform/platform-kit-nextjs/app/api/supabase-proxy/[...path]/route.ts","registry/default/platform/platform-kit-nextjs/components/dynamic-form.tsx","registry/default/platform/platform-kit-nextjs/components/logo-supabase.tsx","registry/default/platform/platform-kit-nextjs/components/results-table.tsx","registry/default/platform/platform-kit-nextjs/components/sql-editor.tsx","registry/default/platform/platform-kit-nextjs/components/supabase-manager/auth.tsx","registry/default/platform/platform-kit-nextjs/components/supabase-manager/database.tsx","registry/default/platform/platform-kit-nextjs/components/supabase-manager/index.tsx","registry/default/platform/platform-kit-nextjs/components/supabase-manager/logs.tsx","registry/default/platform/platform-kit-nextjs/components/supabase-manager/secrets.tsx","registry/default/platform/platform-kit-nextjs/components/supabase-manager/storage.tsx","registry/default/platform/platform-kit-nextjs/components/supabase-manager/suggestions.tsx","registry/default/platform/platform-kit-nextjs/components/supabase-manager/users.tsx","registry/default/platform/platform-kit-nextjs/components/users-growth-chart.tsx","registry/default/platform/platform-kit-nextjs/hooks/use-auth.ts","registry/default/platform/platform-kit-nextjs/hooks/use-logs.ts","registry/default/platform/platform-kit-nextjs/hooks/use-run-query.ts","registry/default/platform/platform-kit-nextjs/hooks/use-secrets.ts","registry/default/platform/platform-kit-nextjs/hooks/use-storage.ts","registry/default/platform/platform-kit-nextjs/hooks/use-suggestions.ts","registry/default/platform/platform-kit-nextjs/hooks/use-tables.ts","registry/default/platform/platform-kit-nextjs/hooks/use-user-counts.ts","registry/default/platform/platform-kit-nextjs/lib/logs.ts","registry/default/platform/platform-kit-nextjs/lib/management-api-schema.d.ts","registry/default/platform/platform-kit-nextjs/lib/management-api.ts","registry/default/platform/platform-kit-nextjs/lib/pg-meta/sql.ts","registry/default/platform/platform-kit-nextjs/lib/pg-meta/index.ts","registry/default/platform/platform-kit-nextjs/lib/pg-meta/types.ts","registry/default/platform/platform-kit-nextjs/lib/schemas/auth.ts","registry/default/platform/platform-kit-nextjs/lib/schemas/secrets.ts","registry/default/platform/platform-kit-nextjs/contexts/SheetNavigationContext.tsx"], + category: "undefined", + subcategory: "undefined", + chunks: [] + } + , "ai-editor-rules": { name: "ai-editor-rules", type: "registry:file", diff --git a/apps/ui-library/components/side-navigation.tsx b/apps/ui-library/components/side-navigation.tsx index 614864cd7b1..ec58a9c4da5 100644 --- a/apps/ui-library/components/side-navigation.tsx +++ b/apps/ui-library/components/side-navigation.tsx @@ -1,7 +1,7 @@ import Link from 'next/link' import NavigationItem from '@/components/side-navigation-item' -import { aiEditorsRules, componentPages, gettingStarted } from '@/config/docs' +import { aiEditorsRules, componentPages, gettingStarted, platformBlocks } from '@/config/docs' import { CommandMenu } from './command-menu' import { ThemeSwitcherDropdown } from './theme-switcher-dropdown' @@ -97,6 +97,14 @@ function SideNavigation() { ))}
+
+
+ {platformBlocks.title} +
+ {platformBlocks.items.map((item, i) => ( + + ))} +
) } diff --git a/apps/ui-library/config/docs.ts b/apps/ui-library/config/docs.ts index 55bfa3b7bbe..c257d7dacf9 100644 --- a/apps/ui-library/config/docs.ts +++ b/apps/ui-library/config/docs.ts @@ -36,6 +36,18 @@ export const aiEditorsRules: SidebarNavGroup = { ], } +export const platformBlocks: SidebarNavGroup = { + title: 'Platform', + items: [ + { + title: 'Platform Kit', + href: '/docs/platform/platform-kit', + items: [], + commandItemLabel: 'Platform Kit', + }, + ], +} + // Component definitions with supported frameworks export const componentPages: SidebarNavGroup = { title: 'Components', diff --git a/apps/ui-library/content/docs/platform/platform-kit.mdx b/apps/ui-library/content/docs/platform/platform-kit.mdx new file mode 100644 index 00000000000..9e9ff343ea9 --- /dev/null +++ b/apps/ui-library/content/docs/platform/platform-kit.mdx @@ -0,0 +1,110 @@ +--- +title: Platform Kit +description: The easiest way to build platforms on top of Supabase +--- + + + +## Installation + + + +## Folder structure + + + +## Introduction + +The Platform Kit is a collection of customizable API's, hooks and components you can use to provide an embedded Supabase experience within your own platform. It comes in the form of a single dialog that enables the management of database, authentication, storage, users, secrets, logs, and performance monitoring. + +**Features** + +- Database, Auth, Storage, User, Secrets, Logs, and Performance management +- Responsive dialog/drawer interface (desktop & mobile) +- API proxy for Management API +- AI-powered SQL generation (optional) +- Customize to your liking + +## Who is it for + +Anyone who is providing Postgres databases to their users. + +## Usage + +Embed the manager dialog in your app and manage its state: + +```tsx +import { useState } from 'react' +import { useMobile } from '@/hooks/use-mobile' +import { Button } from '@/components/ui/button' +import SupabaseManagerDialog from '@/components/supabase-manager' + +export default function Example() { + const [open, setOpen] = useState(false) + const projectRef = 'your-project-ref' // Replace with your actual project ref + const isMobile = useMobile() + + return ( + <> + + + + ) +} +``` + +## Quick Start + +1. **Set up environment variables:** in your `.env.local` file: + + ```bash + SUPABASE_MANAGEMENT_API_TOKEN=your-personal-access-token + NEXT_PUBLIC_ENABLE_AI_QUERIES=true + OPENAI_API_KEY=your-openai-api-key + ``` + +2. **Add project-level authentication checks** in your API proxy at `app/api/supabase-proxy/[...path]/route.ts` as well as your ai/sql route at `app/api/ai/sql/route.ts` to ensure only authorized users can access their own project resources. + +3. **Add a Toaster for notifications:** + Place the following component at the root of your app (e.g., in your `layout.tsx` or `App.tsx`) to enable toast notifications: + + ```tsx + import { Toaster } from '@/components/ui/sonner' + + export default function RootLayout({ children }) { + return ( + + + +
{children}
+ + + + ) + } + ``` + +That's it! The default setup uses your Supabase personal access token for the Management API. + +## Security + +- Never expose your Management API token to the client +- Always implement authentication and permission checks in your proxy + +## Further reading + +- [Supabase Management API](https://supabase.com/docs/reference/api/introduction) diff --git a/apps/ui-library/package.json b/apps/ui-library/package.json index 9bc4d888e92..b47da318fa6 100644 --- a/apps/ui-library/package.json +++ b/apps/ui-library/package.json @@ -5,7 +5,7 @@ "type": "module", "scripts": { "preinstall": "npx only-allow pnpm", - "dev": "next dev --turbopack --port 3004", + "dev": "next dev --port 3004", "build": "pnpm run content:build && pnpm run build:registry && pnpm run build:llms && next build", "build:registry": "tsx --tsconfig ./tsconfig.scripts.json ./scripts/build-registry.mts && prettier --cache --write registry.json && rimraf -G public/r && shadcn build && tsx scripts/clean-registry.ts", "build:llms": "tsx --tsconfig ./tsconfig.scripts.json ./scripts/build-llms-txt.ts", @@ -17,17 +17,44 @@ "typecheck": "contentlayer2 build && tsc --noEmit -p tsconfig.json" }, "dependencies": { + "@hookform/resolvers": "^3.1.1", + "@monaco-editor/react": "^4.6.0", + "@radix-ui/react-accordion": "*", + "@radix-ui/react-alert-dialog": "*", + "@radix-ui/react-aspect-ratio": "*", "@radix-ui/react-avatar": "*", "@radix-ui/react-checkbox": "*", + "@radix-ui/react-collapsible": "*", + "@radix-ui/react-context-menu": "*", + "@radix-ui/react-dialog": "*", + "@radix-ui/react-dropdown-menu": "*", + "@radix-ui/react-hover-card": "*", "@radix-ui/react-label": "*", + "@radix-ui/react-menubar": "*", + "@radix-ui/react-navigation-menu": "*", + "@radix-ui/react-popover": "*", "@radix-ui/react-progress": "*", + "@radix-ui/react-radio-group": "*", + "@radix-ui/react-scroll-area": "*", + "@radix-ui/react-select": "*", + "@radix-ui/react-separator": "*", + "@radix-ui/react-slider": "*", "@radix-ui/react-slot": "*", + "@radix-ui/react-switch": "*", + "@radix-ui/react-tabs": "*", + "@radix-ui/react-toast": "*", + "@radix-ui/react-toggle": "*", + "@radix-ui/react-toggle-group": "*", "@radix-ui/react-tooltip": "*", "@react-router/fs-routes": "^7.4.0", "@supabase/postgrest-js": "*", "@supabase/supa-mdx-lint": "0.2.6-alpha", + "@tanstack/react-query": "^5.83.0", + "axios": "^1.10.0", "class-variance-authority": "*", + "cmdk": "^1.0.0", "common": "workspace:*", + "common-tags": "^1.8.2", "contentlayer2": "0.4.6", "eslint-config-supabase": "workspace:*", "framer-motion": "^11.0.3", @@ -37,11 +64,16 @@ "next": "catalog:", "next-contentlayer2": "0.4.6", "next-themes": "^0.3.0", + "openai": "^5.9.0", + "openapi-fetch": "0.12.4", "react": "catalog:", "react-docgen": "^7.0.3", "react-dom": "catalog:", + "react-hook-form": "^7.45.0", "react-inlinesvg": "^4.0.4", + "react-markdown": "^10.1.0", "react-wrap-balancer": "^1.1.0", + "recharts": "^2.8.0", "rehype-autolink-headings": "^7.1.0", "rehype-pretty-code": "^0.9.0", "rehype-slug": "^6.0.0", @@ -52,6 +84,7 @@ "ui": "workspace:*", "ui-patterns": "workspace:*", "unist-util-visit": "^5.0.0", + "vaul": "^0.9.6", "zod": "^3.22.4" }, "devDependencies": { @@ -61,6 +94,7 @@ "@supabase/supabase-js": "^2.49.1", "@tanstack/react-router": "^1.114.27", "@tanstack/react-start": "^1.114.25", + "@types/common-tags": "^1.8.4", "@types/lodash": "^4.17.16", "@types/react": "catalog:", "@types/react-dom": "catalog:", diff --git a/apps/ui-library/public/llms.txt b/apps/ui-library/public/llms.txt index 863d7d8581e..137187575d0 100644 --- a/apps/ui-library/public/llms.txt +++ b/apps/ui-library/public/llms.txt @@ -1,5 +1,5 @@ # Supabase UI Library -Last updated: 2025-07-07T17:50:06.137Z +Last updated: 2025-07-13T02:57:35.186Z ## Overview Library of components for your project. The components integrate with Supabase and are shadcn compatible. diff --git a/apps/ui-library/public/r/platform-kit-nextjs.json b/apps/ui-library/public/r/platform-kit-nextjs.json new file mode 100644 index 00000000000..7b9a5d2083e --- /dev/null +++ b/apps/ui-library/public/r/platform-kit-nextjs.json @@ -0,0 +1,209 @@ +{ + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "platform-kit-nextjs", + "type": "registry:block", + "title": "Platform Kit for Nextjs and Supabase", + "description": "Platform Kit for Nextjs and Supabase", + "dependencies": [ + "@hookform/resolvers", + "@monaco-editor/react", + "@tanstack/react-query", + "@tanstack/react-table", + "axios", + "cmdk", + "common-tags", + "openai", + "openapi-fetch", + "react-hook-form", + "react-markdown", + "recharts", + "sonner" + ], + "registryDependencies": [ + "alert", + "badge", + "button", + "card", + "chart", + "command", + "dialog", + "drawer", + "form", + "hover-card", + "input", + "label", + "popover", + "select", + "skeleton", + "switch", + "table", + "toggle", + "toggle-group", + "tooltip" + ], + "files": [ + { + "path": "registry/default/platform/platform-kit-nextjs/app/api/ai/sql/route.ts", + "content": "import type { paths } from '@/registry/default/platform/platform-kit-nextjs/lib/management-api-schema'\nimport { listTablesSql } from '@/registry/default/platform/platform-kit-nextjs/lib/pg-meta'\nimport { NextResponse } from 'next/server'\nimport OpenAI from 'openai'\nimport createClient from 'openapi-fetch'\n\nconst openai = new OpenAI({\n apiKey: process.env.OPENAI_API_KEY,\n})\n\nconst client = createClient({\n baseUrl: 'https://api.supabase.com',\n headers: {\n Authorization: `Bearer ${process.env.SUPABASE_MANAGEMENT_API_TOKEN}`,\n },\n})\n\n// Function to get database schema\nasync function getDbSchema(projectRef: string) {\n const token = process.env.SUPABASE_MANAGEMENT_API_TOKEN\n if (!token) {\n throw new Error('Supabase Management API token is not configured.')\n }\n\n const sql = listTablesSql()\n\n const { data, error } = await client.POST('/v1/projects/{ref}/database/query', {\n params: {\n path: {\n ref: projectRef,\n },\n },\n body: {\n query: sql,\n read_only: true,\n },\n })\n\n if (error) {\n throw error\n }\n\n return data as any\n}\n\nfunction formatSchemaForPrompt(schema: any) {\n let schemaString = ''\n if (schema && Array.isArray(schema)) {\n schema.forEach((table: any) => {\n const columnInfo = table.columns.map((c: any) => `${c.name} (${c.data_type})`)\n schemaString += `Table \"${table.name}\" has columns: ${columnInfo.join(', ')}.\\n`\n })\n }\n return schemaString\n}\n\nexport async function POST(request: Request) {\n try {\n const { prompt, projectRef } = await request.json()\n\n if (!prompt) {\n return NextResponse.json({ message: 'Prompt is required.' }, { status: 400 })\n }\n if (!projectRef) {\n return NextResponse.json({ message: 'projectRef is required.' }, { status: 400 })\n }\n\n // Implement your permission check here (e.g. check if the user is a member of the project)\n // In this example, everyone can access all projects\n const userHasPermissionForProject = Boolean(projectRef)\n\n if (!userHasPermissionForProject) {\n return NextResponse.json(\n { message: 'You do not have permission to access this project.' },\n { status: 403 }\n )\n }\n\n // 1. Get database schema\n const schema = await getDbSchema(projectRef)\n const formattedSchema = formatSchemaForPrompt(schema)\n\n // 2. Create a prompt for OpenAI\n const systemPrompt = `You are an expert SQL assistant. Given the following database schema, write a SQL query that answers the user's question. Return only the SQL query, do not include any explanations or markdown.\\n\\nSchema:\\n${formattedSchema}`\n\n // 3. Call OpenAI to generate SQL using responses.create (plain text output)\n const response = await openai.responses.create({\n model: 'gpt-4.1',\n instructions: systemPrompt, // Use systemPrompt as instructions\n input: prompt, // User's question\n })\n\n const sql = response.output_text\n\n if (!sql) {\n return NextResponse.json(\n { message: 'Could not generate SQL from the prompt.' },\n { status: 500 }\n )\n }\n\n // 4. Return the generated SQL\n return NextResponse.json({ sql })\n } catch (error: any) {\n console.error('AI SQL generation error:', error)\n const errorMessage = error.message || 'An unexpected error occurred.'\n const status = error.response?.status || 500\n return NextResponse.json({ message: errorMessage }, { status })\n }\n}\n", + "type": "registry:page", + "target": "app/api/ai/sql/route.ts" + }, + { + "path": "registry/default/platform/platform-kit-nextjs/app/api/supabase-proxy/[...path]/route.ts", + "content": "import { NextResponse } from 'next/server'\n\nasync function forwardToSupabaseAPI(request: Request, method: string, params: { path: string[] }) {\n if (!process.env.SUPABASE_MANAGEMENT_API_TOKEN) {\n console.error('Supabase Management API token is not configured.')\n return NextResponse.json({ message: 'Server configuration error.' }, { status: 500 })\n }\n\n const { path } = params\n const apiPath = path.join('/')\n\n const url = new URL(request.url)\n url.protocol = 'https'\n url.hostname = 'api.supabase.com'\n url.port = '443'\n url.pathname = apiPath\n\n const projectRef = path[2]\n\n // Implement your permission check here (e.g. check if the user is a member of the project)\n // In this example, everyone can access all projects\n const userHasPermissionForProject = Boolean(projectRef)\n\n if (!userHasPermissionForProject) {\n return NextResponse.json(\n { message: 'You do not have permission to access this project.' },\n { status: 403 }\n )\n }\n\n try {\n const forwardHeaders: HeadersInit = {\n Authorization: `Bearer ${process.env.SUPABASE_MANAGEMENT_API_TOKEN}`,\n }\n\n // Copy relevant headers from the original request\n const contentType = request.headers.get('content-type')\n if (contentType) {\n forwardHeaders['Content-Type'] = contentType\n }\n\n const fetchOptions: RequestInit = {\n method,\n headers: forwardHeaders,\n }\n\n // Include body for methods that support it\n if (method !== 'GET' && method !== 'HEAD') {\n try {\n const body = await request.text()\n if (body) {\n fetchOptions.body = body\n }\n } catch (error) {\n // Handle cases where body is not readable\n console.warn('Could not read request body:', error)\n }\n }\n\n const response = await fetch(url, fetchOptions)\n\n // Get response body\n const responseText = await response.text()\n let responseData\n\n try {\n responseData = responseText ? JSON.parse(responseText) : null\n } catch {\n responseData = responseText\n }\n\n // Return the response with the same status\n return NextResponse.json(responseData, { status: response.status })\n } catch (error: any) {\n console.error('Supabase API proxy error:', error)\n const errorMessage = error.message || 'An unexpected error occurred.'\n return NextResponse.json({ message: errorMessage }, { status: 500 })\n }\n}\n\nexport async function GET(request: Request, { params }: { params: Promise<{ path: string[] }> }) {\n const resolvedParams = await params\n return forwardToSupabaseAPI(request, 'GET', resolvedParams)\n}\n\nexport async function HEAD(request: Request, { params }: { params: Promise<{ path: string[] }> }) {\n const resolvedParams = await params\n return forwardToSupabaseAPI(request, 'HEAD', resolvedParams)\n}\n\nexport async function POST(request: Request, { params }: { params: Promise<{ path: string[] }> }) {\n const resolvedParams = await params\n return forwardToSupabaseAPI(request, 'POST', resolvedParams)\n}\n\nexport async function PUT(request: Request, { params }: { params: Promise<{ path: string[] }> }) {\n const resolvedParams = await params\n return forwardToSupabaseAPI(request, 'PUT', resolvedParams)\n}\n\nexport async function DELETE(\n request: Request,\n { params }: { params: Promise<{ path: string[] }> }\n) {\n const resolvedParams = await params\n return forwardToSupabaseAPI(request, 'DELETE', resolvedParams)\n}\n\nexport async function PATCH(request: Request, { params }: { params: Promise<{ path: string[] }> }) {\n const resolvedParams = await params\n return forwardToSupabaseAPI(request, 'PATCH', resolvedParams)\n}\n", + "type": "registry:page", + "target": "app/api/supabase-proxy/[...path]/route.ts" + }, + { + "path": "registry/default/platform/platform-kit-nextjs/components/dynamic-form.tsx", + "content": "'use client'\n\nimport { zodResolver } from '@hookform/resolvers/zod'\nimport { useForm } from 'react-hook-form'\nimport type { z, ZodTypeAny } from 'zod'\nimport { Button } from '@/registry/default/components/ui/button'\nimport {\n Form,\n FormControl,\n FormDescription,\n FormField,\n FormItem,\n FormLabel,\n FormMessage,\n} from '@/registry/default/components/ui/form'\nimport { Input } from '@/registry/default/components/ui/input'\nimport { Switch } from '@/registry/default/components/ui/switch'\nimport {\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from '@/registry/default/components/ui/select'\nimport { useEffect, useRef } from 'react'\n\ninterface DynamicFormProps {\n schema: z.ZodObject\n onSubmit: (data: z.infer>) => void\n isLoading?: boolean\n initialValues?: Partial>>\n labels?: Record }>\n columnInfo?: Record\n}\n\nconst getZodDef = (schema: ZodTypeAny): Record =>\n (schema as any)._def || (schema as any).def\n\nconst unwrapZodType = (fieldSchema: ZodTypeAny): ZodTypeAny => {\n if (\n !fieldSchema ||\n typeof getZodDef(fieldSchema) !== 'object' ||\n getZodDef(fieldSchema) === null\n ) {\n throw new Error(\n `unwrapZodType received an invalid Zod schema object. Check the console for the problematic schema/key.`\n )\n }\n let currentSchema = fieldSchema\n\n // Support both old (typeName) and new (type) Zod formats\n const getTypeName = (def: Record) => def.typeName || def.type\n\n while (\n getTypeName(getZodDef(currentSchema)) === 'ZodOptional' ||\n getTypeName(getZodDef(currentSchema)) === 'optional' ||\n getTypeName(getZodDef(currentSchema)) === 'ZodDefault' ||\n getTypeName(getZodDef(currentSchema)) === 'default' ||\n getTypeName(getZodDef(currentSchema)) === 'ZodNullable' ||\n getTypeName(getZodDef(currentSchema)) === 'nullable' ||\n getTypeName(getZodDef(currentSchema)) === 'ZodEffects' ||\n getTypeName(getZodDef(currentSchema)) === 'effects'\n ) {\n const typeName = getTypeName(getZodDef(currentSchema))\n if (typeName === 'ZodEffects' || typeName === 'effects') {\n // For ZodEffects, get the schema inside the effect\n currentSchema = getZodDef(currentSchema).schema\n } else {\n currentSchema = getZodDef(currentSchema).innerType\n }\n }\n return currentSchema\n}\n\nexport function DynamicForm({\n schema,\n onSubmit,\n isLoading = false,\n initialValues,\n labels,\n columnInfo,\n}: DynamicFormProps) {\n const isInitializingRef = useRef(true)\n\n const defaultValues = Object.keys(schema.shape).reduce(\n (acc, key) => {\n const originalFieldSchema = schema.shape[key]\n if (typeof originalFieldSchema === 'undefined') {\n throw new Error(\n `Schema error: schema.shape['${key}'] is undefined. Check schema definition.`\n )\n }\n\n // Support both old (typeName) and new (type) Zod formats\n const getTypeName = (def: Record) => def.typeName || def.type\n\n if (\n getTypeName(getZodDef(originalFieldSchema)) === 'ZodDefault' ||\n getTypeName(getZodDef(originalFieldSchema)) === 'default'\n ) {\n acc[key] = getZodDef(originalFieldSchema).defaultValue()\n return acc\n }\n\n const baseType = unwrapZodType(originalFieldSchema)\n\n switch (getTypeName(getZodDef(baseType))) {\n case 'ZodString':\n case 'string':\n acc[key] = ''\n break\n case 'ZodBoolean':\n case 'boolean':\n acc[key] = false // Default optional booleans to false\n break\n case 'ZodEnum':\n case 'enum':\n // For enums, use the first enum value as default\n const enumValues = getZodDef(baseType).values || []\n acc[key] = enumValues.length > 0 ? enumValues[0] : ''\n break\n case 'ZodNumber':\n case 'number':\n acc[key] = 0 // Default optional numbers to 0\n break\n case 'ZodArray':\n case 'array':\n acc[key] = [] // Default arrays to empty array\n break\n default:\n acc[key] = undefined // For other types, or if truly no default makes sense\n break\n }\n return acc\n },\n {} as Record\n )\n\n const form = useForm>({\n resolver: zodResolver(schema),\n defaultValues: defaultValues as any,\n })\n\n useEffect(() => {\n if (initialValues) {\n isInitializingRef.current = true\n const schemaKeys = Object.keys(schema.shape)\n const processedInitialValues = schemaKeys.reduce(\n (acc, key) => {\n const fieldDefFromSchema = schema.shape[key]\n if (typeof fieldDefFromSchema === 'undefined') {\n throw new Error(`Schema error in useEffect: schema.shape['${key}'] is undefined.`)\n }\n const value = initialValues.hasOwnProperty(key) ? initialValues[key] : undefined\n const baseFieldType = unwrapZodType(fieldDefFromSchema)\n\n // Support both old (typeName) and new (type) Zod formats\n const getTypeName = (def: Record) => def.typeName || def.type\n\n const fieldTypeName = getTypeName(getZodDef(baseFieldType))\n if (fieldTypeName === 'ZodBoolean' || fieldTypeName === 'boolean') {\n acc[key] = !!value\n } else if (fieldTypeName === 'ZodString' || fieldTypeName === 'string') {\n acc[key] = value === null || value === undefined ? '' : String(value)\n } else if (fieldTypeName === 'ZodEnum' || fieldTypeName === 'enum') {\n const enumValues = getZodDef(baseFieldType).values || []\n acc[key] =\n value === null || value === undefined || !enumValues.includes(value)\n ? enumValues[0] || ''\n : String(value)\n } else if (fieldTypeName === 'ZodNumber' || fieldTypeName === 'number') {\n const num = Number(value)\n acc[key] = isNaN(num) ? 0 : num\n } else if (fieldTypeName === 'ZodArray' || fieldTypeName === 'array') {\n // For arrays, the value should already be an array from the database\n // Handle null values properly\n if (Array.isArray(value)) {\n acc[key] = value\n } else if (value === null || value === undefined) {\n acc[key] = []\n } else if (typeof value === 'string' && value.startsWith('{') && value.endsWith('}')) {\n // Parse PostgreSQL array format like {ONE,TWO} into JavaScript array\n try {\n const innerContent = value.slice(1, -1) // Remove { and }\n if (innerContent.trim() === '') {\n acc[key] = []\n } else {\n // Split by comma and trim whitespace\n acc[key] = innerContent.split(',').map((item: string) => item.trim())\n }\n } catch {\n // If parsing fails, default to empty array\n acc[key] = []\n }\n } else {\n acc[key] = []\n }\n } else {\n acc[key] = value\n }\n return acc\n },\n {} as Record\n )\n form.reset(processedInitialValues as any)\n setTimeout(() => {\n isInitializingRef.current = false\n }, 0)\n } else {\n isInitializingRef.current = false\n }\n }, [initialValues, form, schema])\n\n const renderField = (fieldName: string, fieldSchema: ZodTypeAny) => {\n const baseType = unwrapZodType(fieldSchema)\n // Support both old (typeName) and new (type) Zod formats\n const getTypeName = (def: Record) => def.typeName || def.type\n const typeName = getTypeName(getZodDef(baseType))\n const description = fieldSchema.description\n\n return (\n {\n const labelConfig = labels?.[fieldName]\n const label =\n typeof labelConfig === 'string' ? labelConfig : labelConfig?.label || fieldName\n const typeDisplay = columnInfo?.[fieldName]?.data_type || ''\n switch (typeName) {\n case 'ZodString':\n case 'string':\n return (\n \n
\n
\n {label}\n
{typeDisplay}
\n {description && {description}}\n \n
\n
\n \n \n \n
\n
\n
\n )\n case 'ZodNumber':\n case 'number':\n return (\n \n
\n
\n {label}\n
{typeDisplay}
\n {description && {description}}\n \n
\n
\n \n {\n const value = e.target.value\n const num = parseInt(value, 10)\n field.onChange(isNaN(num) ? undefined : num)\n }}\n />\n \n
\n
\n
\n )\n case 'ZodBoolean':\n case 'boolean':\n return (\n \n
\n {label}\n
{typeDisplay}
\n {description && {description}}\n \n
\n \n \n \n
\n )\n case 'ZodEnum':\n case 'enum':\n const options = getZodDef(baseType).values\n const optionLabels = typeof labelConfig === 'object' ? labelConfig.options : undefined\n return (\n \n
\n
\n {label}\n
{typeDisplay}
\n {description && {description}}\n \n
\n
\n {\n if (!isInitializingRef.current) {\n field.onChange(value)\n }\n }}\n value={String(field.value || '')}\n >\n \n \n \n \n \n \n {options.map((option: string) => (\n \n {optionLabels?.[option] || option}\n \n ))}\n \n \n
\n
\n
\n )\n case 'ZodArray':\n case 'array':\n return (\n \n
\n
\n {label}\n
\n Enter as JSON array: ["item1", "item2"]\n {columnInfo?.[fieldName]?.is_nullable && ' (leave empty for null)'}\n
\n\n {description && {description}}\n \n
\n
\n \n {\n const value = e.target.value\n if (value.trim() === '') {\n // Empty string means null (for nullable) or empty array (for non-nullable)\n field.onChange(null)\n } else {\n try {\n const parsed = JSON.parse(value)\n if (Array.isArray(parsed)) {\n field.onChange(parsed)\n } else {\n // If it's not an array, treat as invalid input\n field.onChange(value)\n }\n } catch {\n // Invalid JSON, keep the string value to show error\n field.onChange(value)\n }\n }\n }}\n />\n \n
\n
\n
\n )\n default:\n return <>\n }\n }}\n />\n )\n }\n\n return (\n
\n \n {Object.keys(schema.shape).map((fieldName) =>\n renderField(fieldName, schema.shape[fieldName])\n )}\n
\n \n
\n
\n \n )\n}\n", + "type": "registry:component" + }, + { + "path": "registry/default/platform/platform-kit-nextjs/components/logo-supabase.tsx", + "content": "export function LogoSupabase({ className, size = 24 }: { className?: string; size?: number }) {\n return (\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n )\n}\n", + "type": "registry:component" + }, + { + "path": "registry/default/platform/platform-kit-nextjs/components/results-table.tsx", + "content": "'use client'\n\nimport {\n Table,\n TableBody,\n TableCell,\n TableHead,\n TableHeader,\n TableRow,\n} from '@/registry/default/components/ui/table'\nimport { cn } from '@/lib/utils'\n\ninterface ResultsTableProps {\n data: any[]\n onRowClick?: (row: any) => void\n}\n\nexport function ResultsTable({ data, onRowClick }: ResultsTableProps) {\n if (!data || data.length === 0) {\n return

The query returned no results.

\n }\n\n const headers = Object.keys(data[0])\n\n return (\n
\n \n \n \n {headers.map((header) => (\n \n {header}\n \n ))}\n \n \n \n {data.map((row, rowIndex) => (\n onRowClick?.(row)}\n className={cn(onRowClick && 'cursor-pointer hover:bg-muted/50 group')}\n >\n {headers.map((header) => (\n \n
\n {JSON.stringify(row[header]).replace(/^\"|\"$/g, '')}\n
\n \n ))}\n \n ))}\n
\n
\n
\n )\n}\n", + "type": "registry:component" + }, + { + "path": "registry/default/platform/platform-kit-nextjs/components/sql-editor.tsx", + "content": "'use client'\n\nimport { useState, useEffect, useCallback, useMemo } from 'react'\nimport { Editor } from '@monaco-editor/react'\nimport { Button } from '@/registry/default/components/ui/button'\nimport { ResultsTable } from '@/registry/default/platform/platform-kit-nextjs/components/results-table'\nimport { Label } from '@/registry/default/components/ui/label'\nimport { useRunQuery } from '@/registry/default/platform/platform-kit-nextjs/hooks/use-run-query'\nimport {\n ArrowUp,\n Loader2,\n BarChart as BarChartIcon,\n Wand,\n FileText,\n AlertTriangle,\n} from 'lucide-react'\nimport { Input } from '@/registry/default/components/ui/input'\nimport { Skeleton } from '@/registry/default/components/ui/skeleton'\nimport { Popover, PopoverContent, PopoverTrigger } from '@/registry/default/components/ui/popover'\nimport {\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from '@/registry/default/components/ui/select'\nimport { Bar, BarChart, CartesianGrid, XAxis, YAxis } from 'recharts'\nimport {\n ChartConfig,\n ChartContainer,\n ChartTooltip,\n ChartTooltipContent,\n} from '@/registry/default/components/ui/chart'\nimport { ToggleGroup, ToggleGroupItem } from '@/registry/default/components/ui/toggle-group'\nimport { Switch } from '@/registry/default/components/ui/switch'\nimport { Alert, AlertDescription, AlertTitle } from '@/registry/default/components/ui/alert'\n\ninterface SqlEditorProps {\n projectRef: string\n initialSql?: string\n queryKey?: any\n label?: string\n onResults?: (data: any[] | undefined) => void\n onRowClick?: (row: any, queryKey?: any) => void\n hideSql?: boolean\n readOnly?: boolean\n runAutomatically?: boolean\n refetch?: number\n initialNaturalLanguageMode?: boolean\n hideChartOption?: boolean\n}\n\nexport function SqlEditor({\n projectRef,\n initialSql,\n queryKey,\n onResults,\n onRowClick,\n label = 'Query your data',\n hideSql = false,\n readOnly = false,\n runAutomatically = false,\n refetch,\n initialNaturalLanguageMode = false,\n hideChartOption = false,\n}: SqlEditorProps) {\n const [sql, setSql] = useState(initialSql || '')\n const [isSqlVisible, setIsSqlVisible] = useState(!hideSql)\n const [isNaturalLanguageMode, setIsNaturalLanguageMode] = useState(\n process.env.NEXT_PUBLIC_ENABLE_AI_QUERIES === 'true' && initialNaturalLanguageMode\n )\n const [naturalLanguageQuery, setNaturalLanguageQuery] = useState('')\n const { mutate: runQuery, data, isPending, error } = useRunQuery()\n const [isGeneratingSql, setIsGeneratingSql] = useState(false)\n const [aiError, setAiError] = useState(null)\n const [isChartVisible, setIsChartVisible] = useState(false)\n const [xAxisColumn, setXAxisColumn] = useState(null)\n const [yAxisColumn, setYAxisColumn] = useState(null)\n\n const columns = useMemo(() => {\n if (!data || data.length === 0) return []\n return Object.keys(data[0])\n }, [data])\n\n useEffect(() => {\n if (initialSql) {\n setSql(initialSql)\n }\n }, [initialSql])\n\n const handleRunNaturalLanguageQuery = async () => {\n if (!naturalLanguageQuery) return\n\n setIsGeneratingSql(true)\n setAiError(null)\n try {\n const response = await fetch('/api/ai/sql', {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n prompt: naturalLanguageQuery,\n projectRef,\n }),\n })\n\n if (!response.ok) {\n const errorData = await response.json()\n throw new Error(errorData.message || 'Failed to generate SQL')\n }\n\n const { sql: generatedSql } = await response.json()\n setSql(generatedSql)\n runQuery({ projectRef, query: generatedSql, readOnly: true })\n } catch (error: any) {\n console.error(error)\n setAiError(error.message)\n } finally {\n setIsGeneratingSql(false)\n }\n }\n\n const handleRunQuery = useCallback(() => {\n if (sql) {\n runQuery({ projectRef, query: sql, readOnly: true })\n }\n }, [sql, projectRef, runQuery])\n\n useEffect(() => {\n setIsSqlVisible(!hideSql)\n }, [hideSql])\n\n useEffect(() => {\n if (runAutomatically && initialSql) {\n runQuery({ projectRef, query: initialSql, readOnly: true })\n }\n }, [runAutomatically, initialSql, projectRef, runQuery])\n\n useEffect(() => {\n if (refetch && refetch > 0) {\n handleRunQuery()\n }\n }, [refetch, handleRunQuery])\n\n useEffect(() => {\n if (onResults) {\n onResults(data)\n }\n }, [data, onResults])\n\n useEffect(() => {\n const noResults = !data || (Array.isArray(data) && data.length === 0)\n if (noResults && !isSqlVisible && !isNaturalLanguageMode && !readOnly && !isPending) {\n setIsSqlVisible(true)\n }\n }, [data, isSqlVisible, isNaturalLanguageMode])\n\n const serverErrorMessage = (error as any)?.response?.data?.message || ''\n const isReadOnlyError =\n serverErrorMessage.includes('permission denied') || serverErrorMessage.includes('42501')\n const customReadOnlyError = \"You can't directly alter your database schema, use chat instead\"\n\n // Build the toggle-group selection based on current UI state\n const toggleValues = useMemo(() => {\n const values: string[] = []\n if (isNaturalLanguageMode) values.push('chat')\n if (isSqlVisible) values.push('sql')\n if (!hideChartOption && isChartVisible) values.push('chart')\n return values\n }, [isNaturalLanguageMode, isSqlVisible, isChartVisible, hideChartOption])\n\n const handleToggleGroupChange = (values: string[]) => {\n setIsNaturalLanguageMode(values.includes('chat'))\n setIsSqlVisible(values.includes('sql'))\n if (!hideChartOption) {\n setIsChartVisible(values.includes('chart'))\n }\n }\n\n return (\n
\n
\n
\n

{label}

\n \n {process.env.NEXT_PUBLIC_ENABLE_AI_QUERIES === 'true' && (\n \n \n \n )}\n \n \n \n {!hideChartOption && (\n \n \n \n \n \n \n \n
\n
\n
\n
\n \n

\n Visualize your data with a chart.\n

\n
\n \n
\n {isChartVisible && (\n
\n
\n \n \n \n \n \n \n {columns.map((col) => (\n \n {col}\n \n ))}\n \n \n
\n
\n \n \n \n \n \n \n {columns.map((col) => (\n \n {col}\n \n ))}\n \n \n
\n
\n )}\n
\n
\n
\n
\n )}\n \n
\n
\n {isNaturalLanguageMode && (\n
\n \n setNaturalLanguageQuery(e.target.value)}\n onKeyDown={(e) => {\n if (e.key === 'Enter' && !e.shiftKey) {\n e.preventDefault()\n handleRunNaturalLanguageQuery()\n }\n }}\n className=\"w-full px-10\"\n />\n \n {isGeneratingSql ? (\n \n ) : isPending ? (\n \n ) : (\n \n )}\n \n
\n )}\n {isSqlVisible && (\n
\n setSql(value || '')}\n theme=\"vs-dark\"\n className=\"bg-transparent\"\n options={{\n minimap: { enabled: false },\n fontSize: 14,\n readOnly,\n padding: {\n top: 24,\n bottom: 24,\n },\n }}\n />\n \n {isPending ? 'Running...' : 'Run Query'}\n \n
\n )}\n
\n
\n {isPending && (\n
\n \n \n \n \n \n \n
\n )}\n {aiError && (\n
\n \n \n Error generating SQL\n {aiError}\n \n
\n )}\n {error && (\n
\n \n \n Query Error\n \n {isReadOnlyError\n ? customReadOnlyError\n : serverErrorMessage || (error as Error)?.message || 'An unexpected error occurred'}\n \n \n
\n )}\n\n {!hideChartOption && data && isChartVisible && xAxisColumn && yAxisColumn && (\n
\n \n
\n )}\n\n {data && (\n
\n onRowClick?.(row, queryKey)} />\n
\n )}\n
\n )\n}\n\nfunction QueryResultChart({ data, xAxis, yAxis }: { data: any[]; xAxis: string; yAxis: string }) {\n const chartConfig = {\n [yAxis]: {\n label: yAxis,\n color: 'var(--chart-1)',\n },\n } satisfies ChartConfig\n\n return (\n \n \n \n \n \n } />\n \n \n \n )\n}\n", + "type": "registry:component" + }, + { + "path": "registry/default/platform/platform-kit-nextjs/components/supabase-manager/auth.tsx", + "content": "'use client'\n\nimport { DynamicForm } from '@/registry/default/platform/platform-kit-nextjs/components/dynamic-form'\nimport {\n useGetAuthConfig,\n useUpdateAuthConfig,\n} from '@/registry/default/platform/platform-kit-nextjs/hooks/use-auth'\nimport {\n authEmailProviderSchema,\n authFieldLabels,\n authGeneralSettingsSchema,\n type AuthGeneralSettingsSchema,\n authGoogleProviderSchema,\n authPhoneProviderSchema,\n} from '../../lib/schemas/auth'\nimport { AlertTriangle, ChevronRight, Mail, Phone, User } from 'lucide-react'\nimport { useCallback, useMemo } from 'react'\nimport { z } from 'zod'\nimport { useSheetNavigation } from '@/registry/default/platform/platform-kit-nextjs/contexts/SheetNavigationContext'\nimport { Skeleton } from '@/registry/default/components/ui/skeleton'\nimport { Button } from '@/registry/default/components/ui/button'\nimport { Alert, AlertDescription, AlertTitle } from '@/registry/default/components/ui/alert'\n\nfunction ProviderSettingsView({\n projectRef,\n schema,\n title,\n initialValues: allInitialValues,\n onSuccess,\n}: {\n projectRef: string\n schema: z.ZodObject | z.ZodEffects>\n title: string\n initialValues: any\n onSuccess: () => void\n}) {\n const { mutate: updateAuthConfig, isPending: isUpdatingConfig } = useUpdateAuthConfig()\n\n const actualSchema = 'shape' in schema ? schema : (schema._def.schema as z.ZodObject)\n\n const handleUpdateAuthConfig = (formData: z.infer) => {\n const payload = Object.fromEntries(\n Object.entries(formData).filter(([_, value]) => value !== undefined)\n )\n\n if (Object.keys(payload).length === 0) {\n alert('No changes to submit. Please modify a field to update.')\n return\n }\n\n updateAuthConfig(\n {\n projectRef,\n payload,\n },\n {\n onSuccess,\n }\n )\n }\n\n const formInitialValues = useMemo(() => {\n if (!allInitialValues) {\n return undefined\n }\n const schemaKeys = Object.keys(actualSchema.shape)\n const result = schemaKeys.reduce(\n (acc, key) => {\n if (Object.prototype.hasOwnProperty.call(allInitialValues, key)) {\n acc[key] = allInitialValues[key]\n }\n return acc\n },\n {} as Record\n )\n\n return result\n }, [allInitialValues, actualSchema])\n\n return (\n
\n

{title}

\n \n
\n )\n}\n\nexport function AuthManager({ projectRef }: { projectRef: string }) {\n const {\n data: authConfigData,\n isLoading: isLoadingConfig,\n error: errorLoadingConfig,\n } = useGetAuthConfig(projectRef)\n\n const { mutate: updateAuthConfig, isPending: isUpdatingConfig } = useUpdateAuthConfig()\n const { push, pop } = useSheetNavigation()\n\n const handleUpdateGeneralSettings = (formData: AuthGeneralSettingsSchema) => {\n const payload = Object.fromEntries(\n Object.entries(formData).filter(([_, value]) => value !== undefined)\n )\n\n if (Object.keys(payload).length === 0) {\n alert('No changes to submit. Please modify a field to update.')\n return\n }\n\n updateAuthConfig({\n projectRef,\n payload,\n })\n }\n\n const providers: {\n name: string\n icon: React.ReactNode\n description: string\n schema: z.ZodObject | z.ZodEffects>\n }[] = [\n {\n icon: ,\n name: 'Email',\n description: 'Sign in with email and password',\n schema: authEmailProviderSchema,\n },\n {\n icon: ,\n name: 'Phone',\n description: 'Sign in with phone number',\n schema: authPhoneProviderSchema,\n },\n {\n icon: ,\n name: 'Google',\n description: 'Sign in with Google',\n schema: authGoogleProviderSchema,\n },\n ]\n\n const handleProviderClick = useCallback(\n (provider: { name: string; schema: z.ZodObject | z.ZodEffects> }) => {\n push({\n title: `${provider.name} Provider Settings`,\n component: (\n pop()}\n />\n ),\n })\n },\n [projectRef, authConfigData, push, pop]\n )\n\n const formInitialValues = useMemo(() => {\n if (!authConfigData) {\n return undefined\n }\n const schemaKeys = Object.keys(authGeneralSettingsSchema.shape)\n return schemaKeys.reduce(\n (acc, key) => {\n if (Object.prototype.hasOwnProperty.call(authConfigData, key)) {\n acc[key] = authConfigData[key as keyof typeof authConfigData]\n }\n return acc\n },\n {} as Record\n )\n }, [authConfigData])\n\n if (errorLoadingConfig) {\n return (\n
\n \n \n Error loading auth settings\n \n There was a problem loading your authentication configuration.\n \n \n
\n )\n }\n\n if (isLoadingConfig)\n return (\n
\n \n \n \n
\n )\n\n return (\n
\n
\n

General Settings

\n

\n Allow people to sign up to your app\n

\n \n
\n
\n

Sign in methods

\n

\n Configure how people sign in and up to your app.\n

\n
\n {providers.map((provider) => (\n handleProviderClick(provider)}\n key={provider.name}\n className=\"rounded-none justify-start flex-row text-left h-auto px-8 py-4 w-full gap-4 border-b last:border-b-0\"\n >\n {provider.icon}\n
\n

{provider.name}

\n

{provider.description}

\n
\n \n \n ))}\n
\n
\n
\n )\n}\n", + "type": "registry:component" + }, + { + "path": "registry/default/platform/platform-kit-nextjs/components/supabase-manager/database.tsx", + "content": "'use client'\n\nimport { useState, useMemo, useCallback } from 'react'\nimport { z, type ZodTypeAny } from 'zod'\nimport { useListTables } from '@/registry/default/platform/platform-kit-nextjs/hooks/use-tables'\nimport { useRunQuery } from '@/registry/default/platform/platform-kit-nextjs/hooks/use-run-query'\nimport { SqlEditor } from '@/registry/default/platform/platform-kit-nextjs/components/sql-editor'\nimport { DynamicForm } from '@/registry/default/platform/platform-kit-nextjs/components/dynamic-form'\nimport { toast } from 'sonner'\nimport { useSheetNavigation } from '@/registry/default/platform/platform-kit-nextjs/contexts/SheetNavigationContext'\nimport { Skeleton } from '@/registry/default/components/ui/skeleton'\nimport { Button } from '@/registry/default/components/ui/button'\nimport { AlertTriangle, Table, Wand } from 'lucide-react'\nimport { Alert, AlertDescription, AlertTitle } from '@/registry/default/components/ui/alert'\n\n// Helper to generate a Zod schema from the table's column definitions\nfunction generateZodSchema(table: any): z.ZodObject {\n if (!table || !table.columns) {\n return z.object({})\n }\n\n const shape: Record = {}\n for (const column of table.columns) {\n // We can only edit updatable columns that are not generated\n if (!column.is_updatable || column.is_generated) continue\n\n let fieldSchema: ZodTypeAny\n const dataType = column.data_type.toLowerCase()\n\n if (dataType.includes('array')) {\n fieldSchema = z.array(z.any())\n } else if (dataType.includes('int') || dataType.includes('numeric')) {\n fieldSchema = z.number()\n } else if (dataType.includes('bool')) {\n fieldSchema = z.boolean()\n } else if (\n dataType === 'user-defined' &&\n column.enums &&\n Array.isArray(column.enums) &&\n column.enums.length > 0\n ) {\n // Handle enum types\n fieldSchema = z.enum(column.enums as [string, ...string[]])\n } else {\n // Default to string for text, varchar, timestamp, uuid, etc.\n fieldSchema = z.string()\n }\n\n if (column.is_nullable) {\n fieldSchema = fieldSchema.nullish()\n }\n\n shape[column.name] = fieldSchema\n }\n return z.object(shape)\n}\n\nconst getPrimaryKeys = (table: any): string[] => {\n if (!table || !table.primary_keys) {\n return []\n }\n return table.primary_keys.map((pk: any) => pk.name)\n}\n\nfunction EditRowView({\n projectRef,\n table,\n row,\n onSuccess,\n}: {\n projectRef: string\n table: any\n row: any\n onSuccess: () => void\n}) {\n const { mutate: runUpdateQuery, isPending: isUpdatePending } = useRunQuery()\n const formSchema = useMemo(() => generateZodSchema(table), [table])\n\n const columnInfo = useMemo(() => {\n if (!table || !table.columns) return {}\n\n const info: Record = {}\n for (const column of table.columns) {\n // Only include updatable columns that are not generated\n if (!column.is_updatable || column.is_generated) continue\n\n const dataType = column.data_type.toLowerCase()\n const displayType =\n dataType === 'user-defined' &&\n column.enums &&\n Array.isArray(column.enums) &&\n column.enums.length > 0\n ? 'enum'\n : dataType\n\n info[column.name] = {\n data_type: displayType,\n is_nullable: column.is_nullable,\n }\n }\n return info\n }, [table])\n\n const handleFormSubmit = useCallback(\n (formData: any) => {\n const pks = getPrimaryKeys(table)\n if (pks.length === 0) {\n toast.error('Cannot update row. This table does not have a primary key.')\n return\n }\n\n const setClauses = Object.entries(formData)\n .map(([key, value]) => {\n if (JSON.stringify(row[key]) === JSON.stringify(value)) return null\n\n // Find the column type to determine formatting\n const column = table.columns.find((col: any) => col.name === key)\n const dataType = column?.data_type?.toLowerCase() || ''\n const isNullable = column?.is_nullable || false\n\n let formattedValue\n\n // Handle empty/null values\n if (\n value === null ||\n value === undefined ||\n (typeof value === 'string' && value.trim() === '')\n ) {\n if (isNullable) {\n formattedValue = 'NULL'\n } else if (dataType.includes('array')) {\n // Non-nullable array, set to empty array\n const jsonObj = JSON.stringify({ [key]: [] })\n formattedValue = `(select ${key} from json_populate_record(null::public.\"${\n table.name\n }\", '${jsonObj.replace(/'/g, \"''\")}'))`\n } else {\n // Non-nullable text field, set to empty string\n formattedValue = \"''\"\n }\n } else if (Array.isArray(value) && value.length === 0) {\n // Explicitly empty array (different from null)\n const jsonObj = JSON.stringify({ [key]: [] })\n formattedValue = `(select ${key} from json_populate_record(null::public.\"${\n table.name\n }\", '${jsonObj.replace(/'/g, \"''\")}'))`\n } else if (dataType.includes('array')) {\n // Array type with actual values - use json_populate_record syntax\n const jsonObj = JSON.stringify({ [key]: value })\n formattedValue = `(select ${key} from json_populate_record(null::public.\"${\n table.name\n }\", '${jsonObj.replace(/'/g, \"''\")}'))`\n } else if (dataType === 'user-defined' && column?.enums) {\n // Handle enum values - treat as strings with proper escaping\n formattedValue = `'${String(value).replace(/'/g, \"''\")}'`\n } else if (typeof value === 'string') {\n formattedValue = `'${value.replace(/'/g, \"''\")}'`\n } else {\n formattedValue = value\n }\n\n return `\"${key}\" = ${formattedValue}`\n })\n .filter(Boolean)\n .join(', ')\n\n if (!setClauses) {\n toast.error('No changes to save')\n onSuccess()\n return\n }\n\n const whereClauses = pks\n .map((pk) => {\n const value = row[pk]\n const formattedValue =\n typeof value === 'string' ? `'${value.replace(/'/g, \"''\")}'` : value\n return `\"${pk}\" = ${formattedValue}`\n })\n .join(' AND ')\n\n const updateSql = `UPDATE public.\"${table.name}\" SET ${setClauses} WHERE ${whereClauses};`\n\n runUpdateQuery({ projectRef, query: updateSql, readOnly: false }, { onSuccess })\n },\n [projectRef, table, row, runUpdateQuery, onSuccess]\n )\n\n return (\n
\n

Editing row in {table.name}

\n \n
\n )\n}\n\nfunction TableRecordsView({ projectRef, table }: { projectRef: string; table: any }) {\n const { push, pop } = useSheetNavigation()\n const [refetchCounter, setRefetchCounter] = useState(0)\n\n const handleRowClick = useCallback(\n (row: any) => {\n push({\n title: `Editing row`,\n component: (\n {\n pop()\n setRefetchCounter((c) => c + 1)\n }}\n />\n ),\n })\n },\n [push, pop, projectRef, table]\n )\n\n return (\n \n )\n}\n\nexport function DatabaseManager({ projectRef }: { projectRef: string }) {\n const { push } = useSheetNavigation()\n const { data: tables, isLoading, isError } = useListTables(projectRef, ['public'])\n\n const handleTableClick = useCallback(\n (table: any) => {\n push({\n title: table.name,\n component: ,\n })\n },\n [push, projectRef]\n )\n\n const handleNaturalLanguageQueryClick = useCallback(() => {\n push({\n title: 'Talk to your database',\n component: (\n \n ),\n })\n }, [push, projectRef])\n\n return (\n
\n
\n
\n

Database

\n

\n View and manage the data stored in your app.\n

\n
\n \n \n Query your database\n \n
\n\n {isError && (\n \n \n Error loading tables\n There was a problem loading your database tables.\n \n )}\n\n {isLoading && (\n
\n \n \n \n \n
\n )}\n\n {tables && tables.length > 0 ? (\n
\n {tables.map((table: any) => (\n handleTableClick(table)}\n >\n \n

{table.name}

\n
\n {table.live_rows_estimate} rows\n
\n \n ))}\n \n ) : !isLoading && (!tables || tables.length === 0) ? (\n \n
\n No database tables\n Create tables to store and organize your data.\n \n ) : null}\n \n )\n}\n", + "type": "registry:component" + }, + { + "path": "registry/default/platform/platform-kit-nextjs/components/supabase-manager/index.tsx", + "content": "'use client'\n\nimport { useState, ReactNode, useMemo } from 'react'\nimport { Button } from '@/registry/default/components/ui/button'\nimport {\n Dialog,\n DialogContent,\n DialogTitle,\n DialogTrigger,\n} from '@/registry/default/components/ui/dialog'\nimport {\n Drawer,\n DrawerContent,\n DrawerHeader,\n DrawerTitle,\n} from '@/registry/default/components/ui/drawer'\nimport { AuthManager } from '@/registry/default/platform/platform-kit-nextjs/components/supabase-manager/auth'\nimport { DatabaseManager } from '@/registry/default/platform/platform-kit-nextjs/components/supabase-manager/database'\nimport { StorageManager } from '@/registry/default/platform/platform-kit-nextjs/components/supabase-manager/storage'\nimport { LogsManager } from '@/registry/default/platform/platform-kit-nextjs/components/supabase-manager/logs'\nimport { SuggestionsManager } from '@/registry/default/platform/platform-kit-nextjs/components/supabase-manager/suggestions'\nimport { UsersManager } from '@/registry/default/platform/platform-kit-nextjs/components/supabase-manager/users'\nimport { SecretsManager } from '@/registry/default/platform/platform-kit-nextjs/components/supabase-manager/secrets'\nimport {\n SheetNavigationProvider,\n useSheetNavigation,\n} from '@/registry/default/platform/platform-kit-nextjs/contexts/SheetNavigationContext'\nimport {\n ChevronLeft,\n ChevronRight,\n Database,\n ExternalLink,\n HardDrive,\n KeyRound,\n Lightbulb,\n ScrollText,\n Shield,\n Users,\n} from 'lucide-react'\nimport {\n HoverCard,\n HoverCardTrigger,\n HoverCardContent,\n} from '@/registry/default/components/ui/hover-card'\nimport { QueryClient, QueryClientProvider } from '@tanstack/react-query'\nimport { LogoSupabase } from '@/registry/default/platform/platform-kit-nextjs/components/logo-supabase'\nimport Link from 'next/link'\n\nconst queryClient = new QueryClient()\n\nfunction DialogView({ projectRef, isMobile }: { projectRef: string; isMobile?: boolean }) {\n const { stack, push, popTo, reset } = useSheetNavigation()\n\n const handleTopLevelNavigation = (title: string, component: ReactNode) => {\n if (stack.length === 1 && stack[0].title === title) {\n return\n }\n reset()\n push({ title, component })\n }\n\n const currentView = stack[stack.length - 1]\n const activeManager = stack.length > 0 ? stack[0].title : null\n\n const navigationItems = useMemo(\n () => [\n {\n title: 'Database',\n icon: Database,\n component: ,\n },\n {\n title: 'Storage',\n icon: HardDrive,\n component: ,\n },\n {\n title: 'Auth',\n icon: Shield,\n component: ,\n },\n {\n title: 'Users',\n icon: Users,\n component: ,\n },\n {\n title: 'Secrets',\n icon: KeyRound,\n component: ,\n },\n {\n title: 'Logs',\n icon: ScrollText,\n component: ,\n },\n {\n title: 'Suggestions',\n icon: Lightbulb,\n component: ,\n },\n ],\n [projectRef]\n )\n\n if (isMobile) {\n return (\n
\n {/* Content Area */}\n
\n
\n {currentView ? (\n currentView.component\n ) : (\n
\n

\n Select a manager from the bottom navigation to get started.\n

\n
\n )}\n
\n
\n\n {/* Bottom Navigation Bar */}\n
\n
\n
\n {navigationItems.map((item) => {\n const Icon = item.icon\n return (\n handleTopLevelNavigation(item.title, item.component)}\n >\n \n \n {item.title === 'Auth' ? 'Auth' : item.title}\n \n \n )\n })}\n
\n
\n
\n
\n )\n }\n\n return (\n
\n {/* Sidebar */}\n
\n
\n

Manage your back-end

\n
\n
\n {navigationItems.map((item) => {\n const Icon = item.icon\n return (\n handleTopLevelNavigation(item.title, item.component)}\n >\n \n {item.title === 'Auth' ? 'Authentication' : item.title}\n \n )\n })}\n
\n
\n \n \n \n \n Open in Supabase\n \n \n \n \n

About Supabase

\n

\n Access powerful back-end tools for database, auth, storage, and logs directly in\n Supabase.\n

\n \n
\n
\n
\n\n {/* Content Area */}\n
\n {/* Header with breadcrumbs */}\n
\n {stack.length > 1 && (\n popTo(stack.length - 2)}\n >\n \n Back\n \n )}\n {/* Breadcrumbs */}\n
\n {stack.map((item: { title: string }, index: number) => (\n
\n {index > 0 && }\n {index === stack.length - 1 ? (\n {item.title}\n ) : (\n \n )}\n
\n ))}\n
\n
\n\n
\n {currentView ? (\n currentView.component\n ) : (\n
\n

\n Select a manager from the sidebar to get started.\n

\n
\n )}\n
\n
\n
\n )\n}\n\nexport default function SupabaseManagerDialog({\n projectRef,\n open,\n onOpenChange,\n isMobile,\n}: {\n projectRef: string\n open: boolean\n onOpenChange: (open: boolean) => void\n isMobile: boolean\n}) {\n const content = (\n {}}\n initialStack={[\n {\n title: 'Database',\n component: ,\n },\n ]}\n >\n \n \n )\n\n if (!isMobile) {\n return (\n \n \n \n Manage your back-end\n {content}\n \n \n \n )\n }\n\n return (\n \n \n \n \n Manage your back-end\n \n {content}\n \n \n \n )\n}\n", + "type": "registry:component" + }, + { + "path": "registry/default/platform/platform-kit-nextjs/components/supabase-manager/logs.tsx", + "content": "'use client'\n\nimport { Alert, AlertDescription, AlertTitle } from '@/registry/default/components/ui/alert'\nimport { Button } from '@/registry/default/components/ui/button'\nimport {\n Command,\n CommandEmpty,\n CommandGroup,\n CommandInput,\n CommandItem,\n CommandList,\n} from '@/registry/default/components/ui/command'\nimport {\n HoverCard,\n HoverCardContent,\n HoverCardTrigger,\n} from '@/registry/default/components/ui/hover-card'\nimport { Popover, PopoverContent, PopoverTrigger } from '@/registry/default/components/ui/popover'\nimport { Skeleton } from '@/registry/default/components/ui/skeleton'\nimport {\n Table,\n TableBody,\n TableCell,\n TableHead,\n TableHeader,\n TableRow,\n} from '@/registry/default/components/ui/table'\nimport { useGetLogs } from '@/registry/default/platform/platform-kit-nextjs/hooks/use-logs'\nimport { LogsTableName, genDefaultQuery } from '../../lib/logs'\nimport { cn } from '@/lib/utils'\nimport { Check, ChevronsUpDown, Logs, Terminal } from 'lucide-react'\nimport { useMemo, useState } from 'react'\n\n// Define log types with names and descriptions\nconst logTypes = [\n {\n value: LogsTableName.FN_EDGE,\n label: 'Function Edge Logs',\n description: 'Edge function execution logs with request/response data',\n },\n {\n value: LogsTableName.AUTH,\n label: 'Authentication Logs',\n description: 'User authentication events and security logs',\n },\n {\n value: LogsTableName.POSTGRES,\n label: 'PostgreSQL Logs',\n description: 'Database queries, errors, and performance metrics',\n },\n {\n value: LogsTableName.REALTIME,\n label: 'Realtime Logs',\n description: 'WebSocket connections and realtime subscriptions',\n },\n {\n value: LogsTableName.STORAGE,\n label: 'Storage Logs',\n description: 'File uploads, downloads, and storage operations',\n },\n {\n value: LogsTableName.PG_CRON,\n label: 'Cron Job Logs',\n description: 'Scheduled job executions and cron task logs',\n },\n {\n value: LogsTableName.EDGE,\n label: 'Edge Logs',\n description: 'HTTP requests and responses from Edge Functions',\n },\n\n {\n value: LogsTableName.FUNCTIONS,\n label: 'Function Logs',\n description: 'Serverless function execution logs and events',\n },\n {\n value: LogsTableName.POSTGREST,\n label: 'PostgREST Logs',\n description: 'API requests to your database through PostgREST',\n },\n {\n value: LogsTableName.SUPAVISOR,\n label: 'Supavisor Logs',\n description: 'Connection pooling and database proxy logs',\n },\n {\n value: LogsTableName.PGBOUNCER,\n label: 'PgBouncer Logs',\n description: 'Legacy connection pooling logs',\n },\n {\n value: LogsTableName.WAREHOUSE,\n label: 'Warehouse Logs',\n description: 'Data warehouse operations and analytics',\n },\n {\n value: LogsTableName.PG_UPGRADE,\n label: 'PostgreSQL Upgrade Logs',\n description: 'Database upgrade processes and migration logs',\n },\n]\n\nexport function LogsManager({ projectRef }: { projectRef: string }) {\n const [activeTab, setActiveTab] = useState(LogsTableName.FN_EDGE)\n const [open, setOpen] = useState(false)\n\n const sql = useMemo(() => genDefaultQuery(activeTab), [activeTab])\n\n const { data: logs, isLoading, error } = useGetLogs(projectRef, { sql })\n\n const selectedLogType = logTypes.find((type) => type.value === activeTab)\n\n return (\n <>\n
\n
\n

Logs

\n

\n Debug errors and track activity in your app\n

\n
\n \n \n \n {selectedLogType ? selectedLogType.label : 'Select log type...'}\n \n \n \n \n \n \n {\n e.stopPropagation()\n }}\n style={{ overscrollBehavior: 'contain' }}\n >\n No log type found.\n \n {logTypes.map((logType) => (\n {\n setActiveTab(currentValue as LogsTableName)\n setOpen(false)\n }}\n className=\"flex items-center gap-2 p-3\"\n >\n
\n
{logType.label}
\n
\n {logType.description}\n
\n
\n \n \n ))}\n
\n \n
\n
\n
\n
\n\n {isLoading && (\n
\n \n \n \n \n
\n )}\n {(error || (logs && logs.error)) && (\n
\n \n \n Error fetching logs\n \n {(error as any)?.message ||\n (typeof logs?.error === 'object' && logs.error?.message) ||\n 'An unexpected error occurred. Please try again.'}\n \n \n
\n )}\n {logs && logs.result && logs.result.length > 0 && (\n
\n
\n \n \n {Object.keys(logs.result[0] as object).map((key, idx, arr) => (\n \n {key.replace(/_/g, ' ').replace(/\\b\\w/g, (l) => l.toUpperCase())}\n \n ))}\n \n \n \n {(logs.result as any[]).map((log, index) => (\n \n {Object.keys(logs.result?.[0] ?? []).map((key, idx, arr) => {\n const value = log[key]\n const formattedValue = (() => {\n if (key === 'timestamp' && typeof value === 'number') {\n return new Date(value / 1000).toLocaleString()\n }\n if (value === null) {\n return 'NULL'\n }\n return typeof value === 'object'\n ? JSON.stringify(value, null, 2)\n : String(value)\n })()\n\n return (\n \n \n \n
\n {formattedValue}\n
\n
\n \n
\n                              {formattedValue}\n                            
\n
\n
\n \n )\n })}\n
\n ))}\n
\n
\n
\n )}\n {logs && logs.result && logs.result.length === 0 && (\n
\n \n \n No logs found\n \n Logs will appear here when your application generates activity.\n \n \n
\n )}\n \n )\n}\n", + "type": "registry:component" + }, + { + "path": "registry/default/platform/platform-kit-nextjs/components/supabase-manager/secrets.tsx", + "content": "'use client'\n\nimport { Button } from '@/registry/default/components/ui/button'\nimport {\n Form,\n FormControl,\n FormField,\n FormItem,\n FormLabel,\n FormMessage,\n} from '@/registry/default/components/ui/form'\nimport { Input } from '@/registry/default/components/ui/input'\nimport { Skeleton } from '@/registry/default/components/ui/skeleton'\nimport {\n useCreateSecrets,\n useDeleteSecrets,\n useGetSecrets,\n} from '@/registry/default/platform/platform-kit-nextjs/hooks/use-secrets'\nimport { secretsSchema } from '../../lib/schemas/secrets'\nimport { zodResolver } from '@hookform/resolvers/zod'\nimport { AlertTriangle, Minus, PlusIcon, Key } from 'lucide-react'\nimport { useFieldArray, useForm } from 'react-hook-form'\nimport { z } from 'zod'\nimport { Alert, AlertDescription, AlertTitle } from '@/registry/default/components/ui/alert'\n\nexport function SecretsManager({ projectRef }: { projectRef: string }) {\n const { data: secrets, isLoading, error } = useGetSecrets(projectRef)\n const { mutate: createSecrets, isPending: isCreating } = useCreateSecrets()\n const { mutate: deleteSecrets, isPending: isDeleting } = useDeleteSecrets()\n const form = useForm>({\n resolver: zodResolver(secretsSchema),\n defaultValues: {\n secrets: [{ name: '', value: '' }],\n },\n })\n\n const { fields, append, remove } = useFieldArray({\n control: form.control,\n name: 'secrets',\n })\n\n const handleCreateSecrets = (formData: z.infer) => {\n createSecrets(\n {\n projectRef,\n secrets: formData.secrets,\n },\n {\n onSuccess: () => {\n form.reset({ secrets: [{ name: '', value: '' }] })\n },\n }\n )\n }\n\n const handleDeleteSecret = (secretName: string) => {\n if (window.confirm(`Are you sure you want to delete the secret \"${secretName}\"?`)) {\n deleteSecrets({\n projectRef,\n secretNames: [secretName],\n })\n }\n }\n\n if (isLoading) {\n return (\n
\n \n \n \n
\n )\n }\n\n if (error) {\n return (\n
\n \n \n Error loading secrets\n \n There was a problem loading your secrets configuration.\n \n \n
\n )\n }\n\n return (\n
\n
\n

Add New Secret

\n

\n Add sensitive information like API keys that your app needs to work securely.\n

\n
\n \n {fields.map((field, index) => (\n
\n (\n \n {index === 0 && Name}\n \n \n \n \n \n )}\n />\n (\n \n {index === 0 && Value}\n \n \n \n \n \n )}\n />\n
\n {index === 0 && Remove}\n remove(index)}\n disabled={fields.length <= 1}\n >\n \n \n
\n
\n ))}\n
\n append({ name: '', value: '' })}\n >\n \n Add Secret\n \n
\n\n \n
\n \n
\n\n
\n

Existing secrets

\n {secrets && secrets.length > 0 ? (\n secrets.map((secret) => (\n \n
\n

{secret.name}

\n {secret.updated_at && (\n

\n Last updated: {new Date(secret.updated_at).toLocaleString()}\n

\n )}\n
\n handleDeleteSecret(secret.name)}\n disabled={isDeleting}\n >\n Delete\n \n
\n ))\n ) : (\n \n \n No secrets found\n No secrets found for this project.\n \n )}\n
\n
\n )\n}\n", + "type": "registry:component" + }, + { + "path": "registry/default/platform/platform-kit-nextjs/components/supabase-manager/storage.tsx", + "content": "'use client'\n\nimport { useGetBuckets } from '@/registry/default/platform/platform-kit-nextjs/hooks/use-storage'\nimport { Skeleton } from '@/registry/default/components/ui/skeleton'\nimport { Badge } from '@/registry/default/components/ui/badge'\nimport { Button } from '@/registry/default/components/ui/button'\nimport { AlertTriangle, Folder } from 'lucide-react'\nimport { Tooltip, TooltipContent, TooltipTrigger } from '@/registry/default/components/ui/tooltip'\nimport { Alert, AlertDescription, AlertTitle } from '@/registry/default/components/ui/alert'\n\nexport function StorageManager({ projectRef }: { projectRef: string }) {\n const { data: buckets, isLoading, isError } = useGetBuckets(projectRef)\n\n return (\n
\n

Storage

\n

\n View and manage the files stored in your app.\n

\n\n {isLoading && (\n
\n \n \n \n \n
\n )}\n {isError && (\n \n \n Error loading buckets\n There was a problem loading your storage buckets.\n \n )}\n\n {buckets && buckets.length > 0 ? (\n
\n {buckets.map((bucket: any) => (\n \n \n \n \n
\n

{bucket.name}

\n\n

\n Updated {new Date(bucket.updated_at).toLocaleDateString()}\n

\n
\n \n {bucket.public ? 'Public' : 'Private'}\n \n \n
\n \n

Viewing files is coming soon

\n
\n
\n ))}\n
\n ) : buckets && buckets.length === 0 ? (\n \n \n No storage buckets\n \n A bucket is a container used to store and protect files in your app.\n \n \n ) : null}\n
\n )\n}\n", + "type": "registry:component" + }, + { + "path": "registry/default/platform/platform-kit-nextjs/components/supabase-manager/suggestions.tsx", + "content": "'use client'\n\nimport { useGetSuggestions } from '@/registry/default/platform/platform-kit-nextjs/hooks/use-suggestions'\nimport { Alert, AlertDescription, AlertTitle } from '@/registry/default/components/ui/alert'\nimport { Terminal } from 'lucide-react'\nimport { Badge } from '@/registry/default/components/ui/badge'\nimport { useMemo } from 'react'\n\nimport ReactMarkdown from 'react-markdown'\nimport { Skeleton } from '@/registry/default/components/ui/skeleton'\n\nexport function SuggestionsManager({ projectRef }: { projectRef: string }) {\n const { data: suggestions, isLoading, error } = useGetSuggestions(projectRef)\n\n const sortedSuggestions = useMemo(() => {\n if (!suggestions) return []\n const levelOrder = { ERROR: 1, WARN: 2, INFO: 3 }\n return [...suggestions].sort((a: any, b: any) => {\n const levelA = levelOrder[a.level as keyof typeof levelOrder] || 99\n const levelB = levelOrder[b.level as keyof typeof levelOrder] || 99\n return levelA - levelB\n })\n }, [suggestions])\n\n const getBadgeVariant = (level: 'ERROR' | 'WARN' | 'INFO') => {\n switch (level) {\n case 'ERROR':\n return 'destructive'\n case 'WARN':\n return 'secondary'\n default:\n return 'outline'\n }\n }\n\n return (\n
\n

Suggestions

\n

\n Improve your project's security and performance.\n

\n {isLoading && (\n
\n \n \n \n \n
\n )}\n {error && (\n \n \n Error fetching suggestions\n \n {(error as any)?.message || 'An unexpected error occurred. Please try again.'}\n \n \n )}\n {suggestions && (\n
\n {sortedSuggestions.length > 0 ? (\n
\n {sortedSuggestions.map((suggestion: any) => (\n \n
\n
\n

{suggestion.title}

\n
\n \n {suggestion.level}\n \n {suggestion.type && (\n \n {suggestion.type.charAt(0).toUpperCase() + suggestion.type.slice(1)}\n \n )}\n
\n
\n
\n \n {children}\n \n ) : (\n
\n                                {children}\n                              
\n )\n },\n }}\n >\n {suggestion.detail}\n \n
\n
\n
\n ))}\n
\n ) : (\n \n \n No suggestions found\n \n Your project looks good! No suggestions at this time.\n \n \n )}\n
\n )}\n
\n )\n}\n", + "type": "registry:component" + }, + { + "path": "registry/default/platform/platform-kit-nextjs/components/supabase-manager/users.tsx", + "content": "'use client'\n\nimport { SqlEditor } from '@/registry/default/platform/platform-kit-nextjs/components/sql-editor'\nimport { useState } from 'react'\nimport {\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from '@/registry/default/components/ui/select'\nimport { UsersGrowthChart } from '@/registry/default/platform/platform-kit-nextjs/components/users-growth-chart'\n\nexport function UsersManager({ projectRef }: { projectRef: string }) {\n const [timeRange, setTimeRange] = useState(90)\n const defaultSql = `SELECT * FROM auth.users ORDER BY created_at DESC LIMIT 100;`\n\n return (\n
\n
\n
\n

Users

\n

\n View user signups over time\n

\n
\n \n
\n
\n \n
\n \n
\n )\n}\n", + "type": "registry:component" + }, + { + "path": "registry/default/platform/platform-kit-nextjs/components/users-growth-chart.tsx", + "content": "'use client'\n\nimport * as React from 'react'\nimport { Bar, BarChart, CartesianGrid, XAxis, YAxis } from 'recharts'\nimport {\n ChartConfig,\n ChartContainer,\n ChartTooltip,\n ChartTooltipContent,\n} from '@/registry/default/components/ui/chart'\nimport { useGetUserCountsByDay } from '@/registry/default/platform/platform-kit-nextjs/hooks/use-user-counts'\nimport { Skeleton } from '@/registry/default/components/ui/skeleton'\nimport { AlertTriangle } from 'lucide-react'\nimport { Alert, AlertDescription, AlertTitle } from '@/registry/default/components/ui/alert'\n\nconst chartConfig = {\n users: {\n label: 'New Users',\n color: 'var(--chart-1)',\n },\n} satisfies ChartConfig\n\nexport function UsersGrowthChart({\n projectRef,\n timeRange,\n}: {\n projectRef: string\n timeRange: number\n}) {\n const { data: chartData, isLoading, isError } = useGetUserCountsByDay(projectRef, timeRange)\n\n return (\n
\n {isLoading && }\n {isError && (\n \n \n Error loading chart data\n There was a problem loading your chart data.\n \n )}\n {chartData && !isLoading && (\n \n \n \n {\n const date = new Date(value)\n return date.toLocaleDateString('en-US', {\n month: 'short',\n day: 'numeric',\n })\n }}\n />\n \n {\n return new Date(value).toLocaleDateString('en-US', {\n month: 'short',\n day: 'numeric',\n year: 'numeric',\n })\n }}\n indicator=\"dot\"\n />\n }\n />\n \n \n \n )}\n
\n )\n}\n", + "type": "registry:component" + }, + { + "path": "registry/default/platform/platform-kit-nextjs/hooks/use-auth.ts", + "content": "'use client'\n\nimport { client } from '@/registry/default/platform/platform-kit-nextjs/lib/management-api'\nimport type { components } from '@/registry/default/platform/platform-kit-nextjs/lib/management-api-schema'\nimport { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'\nimport { type AxiosError } from 'axios'\nimport { toast } from 'sonner'\n\nconst getAuthConfig = async (projectRef: string) => {\n const { data, error } = await client.GET('/v1/projects/{ref}/config/auth', {\n params: {\n path: { ref: projectRef },\n },\n })\n if (error) {\n throw error\n }\n\n return data\n}\n\nexport const useGetAuthConfig = (projectRef: string) => {\n return useQuery({\n queryKey: ['auth-config', projectRef],\n queryFn: () => getAuthConfig(projectRef),\n enabled: !!projectRef,\n retry: false,\n })\n}\n\n// UPDATE Auth Config\nconst updateAuthConfig = async ({\n projectRef,\n payload,\n}: {\n projectRef: string\n payload: components['schemas']['UpdateAuthConfigBody']\n}) => {\n const { data, error } = await client.PATCH('/v1/projects/{ref}/config/auth', {\n params: {\n path: {\n ref: projectRef,\n },\n },\n body: payload,\n })\n if (error) {\n throw error\n }\n\n return data\n}\n\nexport const useUpdateAuthConfig = () => {\n const queryClient = useQueryClient()\n return useMutation({\n mutationFn: updateAuthConfig,\n onSuccess: (data, variables) => {\n toast.success(`Auth config updated.`)\n queryClient.invalidateQueries({\n queryKey: ['auth-config', variables.projectRef],\n })\n },\n onError: (error: AxiosError<{ message: string }>) => {\n toast.error(error.response?.data?.message || 'There was a problem with your request.')\n },\n })\n}\n", + "type": "registry:hook" + }, + { + "path": "registry/default/platform/platform-kit-nextjs/hooks/use-logs.ts", + "content": "'use client'\n\nimport { client } from '@/registry/default/platform/platform-kit-nextjs/lib/management-api'\nimport { useQuery } from '@tanstack/react-query'\n\n// GET Logs\nconst getLogs = async ({\n projectRef,\n iso_timestamp_start,\n iso_timestamp_end,\n sql,\n}: {\n projectRef: string\n iso_timestamp_start?: string\n iso_timestamp_end?: string\n sql?: string\n}) => {\n const { data, error } = await client.GET('/v1/projects/{ref}/analytics/endpoints/logs.all', {\n params: {\n path: {\n ref: projectRef,\n },\n query: {\n iso_timestamp_start,\n iso_timestamp_end,\n sql,\n },\n },\n })\n if (error) {\n throw error\n }\n\n return data\n}\n\nexport const useGetLogs = (\n projectRef: string,\n params: {\n iso_timestamp_start?: string\n iso_timestamp_end?: string\n sql?: string\n } = {}\n) => {\n const queryKey = ['logs', projectRef, params.sql]\n\n return useQuery({\n queryKey: queryKey,\n queryFn: () => {\n const now = new Date()\n const oneHourAgo = new Date(now.getTime() - 60 * 60 * 1000)\n\n const queryParams = {\n sql: params.sql,\n iso_timestamp_start: params.iso_timestamp_start ?? oneHourAgo.toISOString(),\n iso_timestamp_end: params.iso_timestamp_end ?? now.toISOString(),\n }\n return getLogs({ projectRef, ...queryParams })\n },\n enabled: !!projectRef,\n retry: false,\n })\n}\n", + "type": "registry:hook" + }, + { + "path": "registry/default/platform/platform-kit-nextjs/hooks/use-run-query.ts", + "content": "'use client'\n\nimport { client } from '@/registry/default/platform/platform-kit-nextjs/lib/management-api'\nimport { useMutation } from '@tanstack/react-query'\nimport { type AxiosError } from 'axios'\nimport { toast } from 'sonner'\n\n// RUN SQL Query\nexport const runQuery = async ({\n projectRef,\n query,\n readOnly,\n}: {\n projectRef: string\n query: string\n readOnly?: boolean\n}) => {\n const { data, error } = await client.POST('/v1/projects/{ref}/database/query', {\n params: {\n path: {\n ref: projectRef,\n },\n },\n body: {\n query,\n read_only: readOnly,\n },\n })\n\n if (error) {\n throw error\n }\n\n return data as any\n}\n\nexport const useRunQuery = () => {\n return useMutation({\n mutationFn: runQuery,\n onError: (error: AxiosError<{ message: string }>) => {\n toast.error(error.response?.data?.message || 'There was a problem with your query.')\n },\n })\n}\n", + "type": "registry:hook" + }, + { + "path": "registry/default/platform/platform-kit-nextjs/hooks/use-secrets.ts", + "content": "'use client'\n\nimport { client } from '@/registry/default/platform/platform-kit-nextjs/lib/management-api'\nimport type { components } from '@/registry/default/platform/platform-kit-nextjs/lib/management-api-schema'\nimport { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'\nimport { type AxiosError } from 'axios'\nimport { toast } from 'sonner'\n\n// GET Secrets\nconst getSecrets = async (projectRef: string) => {\n const { data, error } = await client.GET('/v1/projects/{ref}/secrets', {\n params: {\n path: {\n ref: projectRef,\n },\n },\n })\n if (error) {\n throw error\n }\n\n return data\n}\n\nexport const useGetSecrets = (projectRef: string) => {\n return useQuery({\n queryKey: ['secrets', projectRef],\n queryFn: () => getSecrets(projectRef),\n enabled: !!projectRef,\n retry: false,\n })\n}\n\n// CREATE Secrets\nconst createSecrets = async ({\n projectRef,\n secrets,\n}: {\n projectRef: string\n secrets: components['schemas']['CreateSecretBody']\n}) => {\n const { data, error } = await client.POST('/v1/projects/{ref}/secrets', {\n params: {\n path: {\n ref: projectRef,\n },\n },\n body: secrets,\n })\n if (error) {\n throw error\n }\n\n return data\n}\n\nexport const useCreateSecrets = () => {\n const queryClient = useQueryClient()\n return useMutation({\n mutationFn: createSecrets,\n onSuccess: (data, variables) => {\n toast.success(`Secrets created successfully.`)\n queryClient.refetchQueries({\n queryKey: ['secrets', variables.projectRef],\n })\n },\n onError: (error: AxiosError<{ message: string }>) => {\n toast.error(error.response?.data?.message || 'There was a problem with your request.')\n },\n })\n}\n\n// DELETE Secrets\nconst deleteSecrets = async ({\n projectRef,\n secretNames,\n}: {\n projectRef: string\n secretNames: string[]\n}) => {\n const { data, error } = await client.DELETE('/v1/projects/{ref}/secrets', {\n params: {\n path: {\n ref: projectRef,\n },\n },\n body: secretNames,\n })\n if (error) {\n throw error\n }\n\n return data\n}\n\nexport const useDeleteSecrets = () => {\n const queryClient = useQueryClient()\n return useMutation({\n mutationFn: deleteSecrets,\n onSuccess: (data, variables) => {\n toast.success(`Secrets deleted successfully.`)\n queryClient.invalidateQueries({\n queryKey: ['secrets', variables.projectRef],\n })\n },\n onError: (error: AxiosError<{ message: string }>) => {\n toast.error(error.response?.data?.message || 'There was a problem with your request.')\n },\n })\n}\n", + "type": "registry:hook" + }, + { + "path": "registry/default/platform/platform-kit-nextjs/hooks/use-storage.ts", + "content": "'use client'\n\nimport { client } from '@/registry/default/platform/platform-kit-nextjs/lib/management-api'\nimport { useQuery } from '@tanstack/react-query'\n\n// GET Buckets\nconst getBuckets = async (projectRef: string) => {\n const { data, error } = await client.GET('/v1/projects/{ref}/storage/buckets', {\n params: {\n path: {\n ref: projectRef,\n },\n },\n })\n if (error) {\n throw error\n }\n\n return data\n}\n\nexport const useGetBuckets = (projectRef: string) => {\n return useQuery({\n queryKey: ['buckets', projectRef],\n queryFn: () => getBuckets(projectRef),\n enabled: !!projectRef,\n retry: false,\n })\n}\n\n// LIST Objects\nconst listObjects = async ({ projectRef, bucketId }: { projectRef: string; bucketId: string }) => {\n const { data, error } = await client.POST(\n // TODO\n // @ts-expect-error this endpoint is not yet implemented\n '/v1/projects/{ref}/storage/buckets/{bucketId}/objects/list',\n {\n params: {\n path: {\n ref: projectRef,\n bucketId,\n },\n },\n body: {\n path: '',\n options: { limit: 100, offset: 0 },\n },\n }\n )\n if (error) {\n throw error\n }\n\n return data as any\n}\n\nexport const useListObjects = (projectRef: string, bucketId: string) => {\n return useQuery({\n queryKey: ['objects', projectRef, bucketId],\n queryFn: () => listObjects({ projectRef, bucketId }),\n enabled: !!projectRef && !!bucketId,\n })\n}\n", + "type": "registry:hook" + }, + { + "path": "registry/default/platform/platform-kit-nextjs/hooks/use-suggestions.ts", + "content": "'use client'\n\nimport { client } from '@/registry/default/platform/platform-kit-nextjs/lib/management-api'\nimport { useQuery } from '@tanstack/react-query'\n\n// GET Suggestions\nconst getSuggestions = async (projectRef: string) => {\n const [\n { data: performanceData, error: performanceError },\n { data: securityData, error: securityError },\n ] = await Promise.all([\n client.GET('/v1/projects/{ref}/advisors/performance', {\n params: {\n path: {\n ref: projectRef,\n },\n },\n }),\n client.GET('/v1/projects/{ref}/advisors/security', {\n params: {\n path: {\n ref: projectRef,\n },\n },\n }),\n ])\n if (performanceError) {\n throw performanceError\n }\n if (securityError) {\n throw securityError\n }\n\n // Add type to each suggestion\n const performanceLints = (performanceData?.lints || []).map((lint) => ({\n ...lint,\n type: 'performance' as const,\n }))\n const securityLints = (securityData?.lints || []).map((lint) => ({\n ...lint,\n type: 'security' as const,\n }))\n return [...performanceLints, ...securityLints]\n}\n\nexport const useGetSuggestions = (projectRef: string) => {\n return useQuery({\n queryKey: ['suggestions', projectRef],\n queryFn: () => getSuggestions(projectRef),\n enabled: !!projectRef,\n retry: false,\n })\n}\n", + "type": "registry:hook" + }, + { + "path": "registry/default/platform/platform-kit-nextjs/hooks/use-tables.ts", + "content": "'use client'\n\nimport { listTablesSql } from '@/registry/default/platform/platform-kit-nextjs/lib/pg-meta'\nimport { runQuery } from '@/registry/default/platform/platform-kit-nextjs/hooks/use-run-query'\nimport { useQuery } from '@tanstack/react-query'\n\n// LIST Tables\nconst listTables = ({ projectRef, schemas }: { projectRef: string; schemas?: string[] }) => {\n const sql = listTablesSql(schemas)\n return runQuery({\n projectRef,\n query: sql,\n readOnly: true,\n })\n}\n\nexport const useListTables = (projectRef: string, schemas?: string[]) => {\n return useQuery({\n queryKey: ['tables', projectRef, schemas],\n queryFn: () => listTables({ projectRef, schemas }),\n enabled: !!projectRef,\n })\n}\n", + "type": "registry:hook" + }, + { + "path": "registry/default/platform/platform-kit-nextjs/hooks/use-user-counts.ts", + "content": "'use client'\n\nimport { useQuery } from '@tanstack/react-query'\nimport { runQuery } from '@/registry/default/platform/platform-kit-nextjs/hooks/use-run-query'\n\n// GET User Counts by day\nconst getUserCountsByDay = ({ projectRef, days }: { projectRef: string; days: number }) => {\n const sql = `\n WITH days_series AS (\n SELECT generate_series(\n date_trunc('day', now() - interval '${Number(days) - 1} days'),\n date_trunc('day', now()),\n '1 day'::interval\n )::date AS date\n )\n SELECT\n d.date,\n COALESCE(u.users, 0)::int as users\n FROM\n days_series d\n LEFT JOIN (\n SELECT\n date_trunc('day', created_at AT TIME ZONE 'UTC')::date as date,\n count(id) as users\n FROM\n auth.users\n GROUP BY 1\n ) u ON d.date = u.date\n ORDER BY\n d.date ASC;\n `\n\n return runQuery({\n projectRef,\n query: sql,\n readOnly: true,\n })\n}\n\nexport const useGetUserCountsByDay = (projectRef: string, days: number) => {\n return useQuery({\n queryKey: ['user-counts', projectRef, days],\n queryFn: () => getUserCountsByDay({ projectRef, days }),\n enabled: !!projectRef,\n retry: false,\n })\n}\n", + "type": "registry:hook" + }, + { + "path": "registry/default/platform/platform-kit-nextjs/lib/logs.ts", + "content": "export enum LogsTableName {\n FN_EDGE = 'function_edge_logs',\n AUTH = 'auth_logs',\n POSTGRES = 'postgres_logs',\n REALTIME = 'realtime_logs',\n STORAGE = 'storage_logs',\n PG_CRON = 'pg_cron_logs',\n EDGE = 'edge_logs',\n FUNCTIONS = 'function_logs',\n POSTGREST = 'postgrest_logs',\n SUPAVISOR = 'supavisor_logs',\n PGBOUNCER = 'pgbouncer_logs',\n WAREHOUSE = 'warehouse_logs',\n PG_UPGRADE = 'pg_upgrade_logs',\n}\n\nconst genCrossJoinUnnests = (table: LogsTableName) => {\n switch (table) {\n case LogsTableName.EDGE:\n return `cross join unnest(metadata) as m\n cross join unnest(m.request) as request\n cross join unnest(m.response) as response`\n case LogsTableName.POSTGRES:\n return `cross join unnest(metadata) as m\n cross join unnest(m.parsed) as parsed`\n case LogsTableName.FUNCTIONS:\n return `cross join unnest(metadata) as metadata`\n case LogsTableName.AUTH:\n return `cross join unnest(metadata) as metadata`\n case LogsTableName.FN_EDGE:\n return `cross join unnest(metadata) as m\n cross join unnest(m.response) as response\n cross join unnest(m.request) as request`\n case LogsTableName.SUPAVISOR:\n return `cross join unnest(metadata) as m`\n default:\n return ''\n }\n}\n\nexport const genDefaultQuery = (table: LogsTableName, limit: number = 100) => {\n const joins = genCrossJoinUnnests(table)\n const orderBy = 'order by timestamp desc'\n\n switch (table) {\n case LogsTableName.EDGE:\n return `select id, ${table}.timestamp, event_message, request.method, request.path, request.search, response.status_code\nfrom ${table}\n${joins}\n${orderBy}\nlimit ${limit}`\n\n case LogsTableName.POSTGRES:\n return `select ${table}.timestamp, id, event_message, parsed.error_severity, parsed.detail, parsed.hint\nfrom ${table}\n${joins}\n${orderBy}\nlimit ${limit}`\n\n case LogsTableName.FUNCTIONS:\n return `select id, ${table}.timestamp, event_message, metadata.event_type, metadata.function_id, metadata.level\nfrom ${table}\n${joins}\n${orderBy}\nlimit ${limit}`\n\n case LogsTableName.AUTH:\n return `select id, ${table}.timestamp, event_message, metadata.level, metadata.status, metadata.path, metadata.msg as msg, metadata.error\nfrom ${table}\n${joins}\n${orderBy}\nlimit ${limit}`\n\n case LogsTableName.FN_EDGE:\n return `select id, ${table}.timestamp, event_message, response.status_code, request.method, m.function_id, m.execution_time_ms, m.deployment_id, m.version\nfrom ${table}\n${joins}\n${orderBy}\nlimit ${limit}`\n\n case LogsTableName.SUPAVISOR:\n return `select id, ${table}.timestamp, event_message\nfrom ${table}\n${joins}\n${orderBy}\nlimit ${limit}`\n\n case LogsTableName.PG_UPGRADE:\n return `select id, ${table}.timestamp, event_message\nfrom ${table}\n${joins}\n${orderBy}\nlimit ${limit}`\n\n case LogsTableName.PG_CRON:\n return `select postgres_logs.timestamp, id, event_message, parsed.error_severity, parsed.query\nfrom postgres_logs\ncross join unnest(metadata) as m\ncross join unnest(m.parsed) as parsed\nwhere (parsed.application_name = 'pg_cron' OR event_message LIKE '%cron job%')\n${orderBy}\nlimit ${limit}`\n\n default:\n return `select id, ${table}.timestamp, event_message\nfrom ${table}\n${joins}\n${orderBy}\nlimit ${limit}`\n }\n}\n", + "type": "registry:lib" + }, + { + "path": "registry/default/platform/platform-kit-nextjs/lib/management-api-schema.d.ts", + "content": "/**\n * This file was auto-generated by openapi-typescript.\n * Do not make direct changes to the file.\n */\n\nexport interface paths {\n '/v1/branches/{branch_id}': {\n parameters: {\n query?: never\n header?: never\n path?: never\n cookie?: never\n }\n /**\n * Get database branch config\n * @description Fetches configurations of the specified database branch\n */\n get: operations['v1-get-a-branch-config']\n put?: never\n post?: never\n /**\n * Delete a database branch\n * @description Deletes the specified database branch\n */\n delete: operations['v1-delete-a-branch']\n options?: never\n head?: never\n /**\n * Update database branch config\n * @description Updates the configuration of the specified database branch\n */\n patch: operations['v1-update-a-branch-config']\n trace?: never\n }\n '/v1/branches/{branch_id}/push': {\n parameters: {\n query?: never\n header?: never\n path?: never\n cookie?: never\n }\n get?: never\n put?: never\n /**\n * Pushes a database branch\n * @description Pushes the specified database branch\n */\n post: operations['v1-push-a-branch']\n delete?: never\n options?: never\n head?: never\n patch?: never\n trace?: never\n }\n '/v1/branches/{branch_id}/merge': {\n parameters: {\n query?: never\n header?: never\n path?: never\n cookie?: never\n }\n get?: never\n put?: never\n /**\n * Merges a database branch\n * @description Merges the specified database branch\n */\n post: operations['v1-merge-a-branch']\n delete?: never\n options?: never\n head?: never\n patch?: never\n trace?: never\n }\n '/v1/branches/{branch_id}/reset': {\n parameters: {\n query?: never\n header?: never\n path?: never\n cookie?: never\n }\n get?: never\n put?: never\n /**\n * Resets a database branch\n * @description Resets the specified database branch\n */\n post: operations['v1-reset-a-branch']\n delete?: never\n options?: never\n head?: never\n patch?: never\n trace?: never\n }\n '/v1/branches/{branch_id}/diff': {\n parameters: {\n query?: never\n header?: never\n path?: never\n cookie?: never\n }\n /**\n * [Beta] Diffs a database branch\n * @description Diffs the specified database branch\n */\n get: operations['v1-diff-a-branch']\n put?: never\n post?: never\n delete?: never\n options?: never\n head?: never\n patch?: never\n trace?: never\n }\n '/v1/projects': {\n parameters: {\n query?: never\n header?: never\n path?: never\n cookie?: never\n }\n /**\n * List all projects\n * @description Returns a list of all projects you've previously created.\n */\n get: operations['v1-list-all-projects']\n put?: never\n /** Create a project */\n post: operations['v1-create-a-project']\n delete?: never\n options?: never\n head?: never\n patch?: never\n trace?: never\n }\n '/v1/organizations': {\n parameters: {\n query?: never\n header?: never\n path?: never\n cookie?: never\n }\n /**\n * List all organizations\n * @description Returns a list of organizations that you currently belong to.\n */\n get: operations['v1-list-all-organizations']\n put?: never\n /** Create an organization */\n post: operations['v1-create-an-organization']\n delete?: never\n options?: never\n head?: never\n patch?: never\n trace?: never\n }\n '/v1/oauth/authorize': {\n parameters: {\n query?: never\n header?: never\n path?: never\n cookie?: never\n }\n /** [Beta] Authorize user through oauth */\n get: operations['v1-authorize-user']\n put?: never\n post?: never\n delete?: never\n options?: never\n head?: never\n patch?: never\n trace?: never\n }\n '/v1/oauth/token': {\n parameters: {\n query?: never\n header?: never\n path?: never\n cookie?: never\n }\n get?: never\n put?: never\n /** [Beta] Exchange auth code for user's access and refresh token */\n post: operations['v1-exchange-oauth-token']\n delete?: never\n options?: never\n head?: never\n patch?: never\n trace?: never\n }\n '/v1/oauth/revoke': {\n parameters: {\n query?: never\n header?: never\n path?: never\n cookie?: never\n }\n get?: never\n put?: never\n /** [Beta] Revoke oauth app authorization and it's corresponding tokens */\n post: operations['v1-revoke-token']\n delete?: never\n options?: never\n head?: never\n patch?: never\n trace?: never\n }\n '/v1/oauth/authorize/project-claim': {\n parameters: {\n query?: never\n header?: never\n path?: never\n cookie?: never\n }\n /**\n * Authorize user through oauth and claim a project\n * @description Initiates the OAuth authorization flow for the specified provider. After successful authentication, the user can claim ownership of the specified project.\n */\n get: operations['v1-oauth-authorize-project-claim']\n put?: never\n post?: never\n delete?: never\n options?: never\n head?: never\n patch?: never\n trace?: never\n }\n '/v1/snippets': {\n parameters: {\n query?: never\n header?: never\n path?: never\n cookie?: never\n }\n /** Lists SQL snippets for the logged in user */\n get: operations['v1-list-all-snippets']\n put?: never\n post?: never\n delete?: never\n options?: never\n head?: never\n patch?: never\n trace?: never\n }\n '/v1/snippets/{id}': {\n parameters: {\n query?: never\n header?: never\n path?: never\n cookie?: never\n }\n /** Gets a specific SQL snippet */\n get: operations['v1-get-a-snippet']\n put?: never\n post?: never\n delete?: never\n options?: never\n head?: never\n patch?: never\n trace?: never\n }\n '/v1/projects/{ref}/api-keys': {\n parameters: {\n query?: never\n header?: never\n path?: never\n cookie?: never\n }\n /** Get project api keys */\n get: operations['v1-get-project-api-keys']\n put?: never\n /** Creates a new API key for the project */\n post: operations['v1-create-project-api-key']\n delete?: never\n options?: never\n head?: never\n patch?: never\n trace?: never\n }\n '/v1/projects/{ref}/api-keys/legacy': {\n parameters: {\n query?: never\n header?: never\n path?: never\n cookie?: never\n }\n /** Check whether JWT based legacy (anon, service_role) API keys are enabled. This API endpoint will be removed in the future, check for HTTP 404 Not Found. */\n get: operations['v1-get-project-legacy-api-keys']\n /** Disable or re-enable JWT based legacy (anon, service_role) API keys. This API endpoint will be removed in the future, check for HTTP 404 Not Found. */\n put: operations['v1-update-project-legacy-api-keys']\n post?: never\n delete?: never\n options?: never\n head?: never\n patch?: never\n trace?: never\n }\n '/v1/projects/{ref}/api-keys/{id}': {\n parameters: {\n query?: never\n header?: never\n path?: never\n cookie?: never\n }\n /** Get API key */\n get: operations['v1-get-project-api-key']\n put?: never\n post?: never\n /** Deletes an API key for the project */\n delete: operations['v1-delete-project-api-key']\n options?: never\n head?: never\n /** Updates an API key for the project */\n patch: operations['v1-update-project-api-key']\n trace?: never\n }\n '/v1/projects/{ref}/branches': {\n parameters: {\n query?: never\n header?: never\n path?: never\n cookie?: never\n }\n /**\n * List all database branches\n * @description Returns all database branches of the specified project.\n */\n get: operations['v1-list-all-branches']\n put?: never\n /**\n * Create a database branch\n * @description Creates a database branch from the specified project.\n */\n post: operations['v1-create-a-branch']\n /**\n * Disables preview branching\n * @description Disables preview branching for the specified project\n */\n delete: operations['v1-disable-preview-branching']\n options?: never\n head?: never\n patch?: never\n trace?: never\n }\n '/v1/projects/{ref}/custom-hostname': {\n parameters: {\n query?: never\n header?: never\n path?: never\n cookie?: never\n }\n /** [Beta] Gets project's custom hostname config */\n get: operations['v1-get-hostname-config']\n put?: never\n post?: never\n /** [Beta] Deletes a project's custom hostname configuration */\n delete: operations['v1-Delete hostname config']\n options?: never\n head?: never\n patch?: never\n trace?: never\n }\n '/v1/projects/{ref}/custom-hostname/initialize': {\n parameters: {\n query?: never\n header?: never\n path?: never\n cookie?: never\n }\n get?: never\n put?: never\n /** [Beta] Updates project's custom hostname configuration */\n post: operations['v1-update-hostname-config']\n delete?: never\n options?: never\n head?: never\n patch?: never\n trace?: never\n }\n '/v1/projects/{ref}/custom-hostname/reverify': {\n parameters: {\n query?: never\n header?: never\n path?: never\n cookie?: never\n }\n get?: never\n put?: never\n /** [Beta] Attempts to verify the DNS configuration for project's custom hostname configuration */\n post: operations['v1-verify-dns-config']\n delete?: never\n options?: never\n head?: never\n patch?: never\n trace?: never\n }\n '/v1/projects/{ref}/custom-hostname/activate': {\n parameters: {\n query?: never\n header?: never\n path?: never\n cookie?: never\n }\n get?: never\n put?: never\n /** [Beta] Activates a custom hostname for a project. */\n post: operations['v1-activate-custom-hostname']\n delete?: never\n options?: never\n head?: never\n patch?: never\n trace?: never\n }\n '/v1/projects/{ref}/network-bans/retrieve': {\n parameters: {\n query?: never\n header?: never\n path?: never\n cookie?: never\n }\n get?: never\n put?: never\n /** [Beta] Gets project's network bans */\n post: operations['v1-list-all-network-bans']\n delete?: never\n options?: never\n head?: never\n patch?: never\n trace?: never\n }\n '/v1/projects/{ref}/network-bans/retrieve/enriched': {\n parameters: {\n query?: never\n header?: never\n path?: never\n cookie?: never\n }\n get?: never\n put?: never\n /** [Beta] Gets project's network bans with additional information about which databases they affect */\n post: operations['v1-list-all-network-bans-enriched']\n delete?: never\n options?: never\n head?: never\n patch?: never\n trace?: never\n }\n '/v1/projects/{ref}/network-bans': {\n parameters: {\n query?: never\n header?: never\n path?: never\n cookie?: never\n }\n get?: never\n put?: never\n post?: never\n /** [Beta] Remove network bans. */\n delete: operations['v1-delete-network-bans']\n options?: never\n head?: never\n patch?: never\n trace?: never\n }\n '/v1/projects/{ref}/network-restrictions': {\n parameters: {\n query?: never\n header?: never\n path?: never\n cookie?: never\n }\n /** [Beta] Gets project's network restrictions */\n get: operations['v1-get-network-restrictions']\n put?: never\n post?: never\n delete?: never\n options?: never\n head?: never\n patch?: never\n trace?: never\n }\n '/v1/projects/{ref}/network-restrictions/apply': {\n parameters: {\n query?: never\n header?: never\n path?: never\n cookie?: never\n }\n get?: never\n put?: never\n /** [Beta] Updates project's network restrictions */\n post: operations['v1-update-network-restrictions']\n delete?: never\n options?: never\n head?: never\n patch?: never\n trace?: never\n }\n '/v1/projects/{ref}/pgsodium': {\n parameters: {\n query?: never\n header?: never\n path?: never\n cookie?: never\n }\n /** [Beta] Gets project's pgsodium config */\n get: operations['v1-get-pgsodium-config']\n /** [Beta] Updates project's pgsodium config. Updating the root_key can cause all data encrypted with the older key to become inaccessible. */\n put: operations['v1-update-pgsodium-config']\n post?: never\n delete?: never\n options?: never\n head?: never\n patch?: never\n trace?: never\n }\n '/v1/projects/{ref}/postgrest': {\n parameters: {\n query?: never\n header?: never\n path?: never\n cookie?: never\n }\n /** Gets project's postgrest config */\n get: operations['v1-get-postgrest-service-config']\n put?: never\n post?: never\n delete?: never\n options?: never\n head?: never\n /** Updates project's postgrest config */\n patch: operations['v1-update-postgrest-service-config']\n trace?: never\n }\n '/v1/projects/{ref}': {\n parameters: {\n query?: never\n header?: never\n path?: never\n cookie?: never\n }\n /** Gets a specific project that belongs to the authenticated user */\n get: operations['v1-get-project']\n put?: never\n post?: never\n /** Deletes the given project */\n delete: operations['v1-delete-a-project']\n options?: never\n head?: never\n patch?: never\n trace?: never\n }\n '/v1/projects/{ref}/secrets': {\n parameters: {\n query?: never\n header?: never\n path?: never\n cookie?: never\n }\n /**\n * List all secrets\n * @description Returns all secrets you've previously added to the specified project.\n */\n get: operations['v1-list-all-secrets']\n put?: never\n /**\n * Bulk create secrets\n * @description Creates multiple secrets and adds them to the specified project.\n */\n post: operations['v1-bulk-create-secrets']\n /**\n * Bulk delete secrets\n * @description Deletes all secrets with the given names from the specified project\n */\n delete: operations['v1-bulk-delete-secrets']\n options?: never\n head?: never\n patch?: never\n trace?: never\n }\n '/v1/projects/{ref}/ssl-enforcement': {\n parameters: {\n query?: never\n header?: never\n path?: never\n cookie?: never\n }\n /** [Beta] Get project's SSL enforcement configuration. */\n get: operations['v1-get-ssl-enforcement-config']\n /** [Beta] Update project's SSL enforcement configuration. */\n put: operations['v1-update-ssl-enforcement-config']\n post?: never\n delete?: never\n options?: never\n head?: never\n patch?: never\n trace?: never\n }\n '/v1/projects/{ref}/types/typescript': {\n parameters: {\n query?: never\n header?: never\n path?: never\n cookie?: never\n }\n /**\n * Generate TypeScript types\n * @description Returns the TypeScript types of your schema for use with supabase-js.\n */\n get: operations['v1-generate-typescript-types']\n put?: never\n post?: never\n delete?: never\n options?: never\n head?: never\n patch?: never\n trace?: never\n }\n '/v1/projects/{ref}/vanity-subdomain': {\n parameters: {\n query?: never\n header?: never\n path?: never\n cookie?: never\n }\n /** [Beta] Gets current vanity subdomain config */\n get: operations['v1-get-vanity-subdomain-config']\n put?: never\n post?: never\n /** [Beta] Deletes a project's vanity subdomain configuration */\n delete: operations['v1-deactivate-vanity-subdomain-config']\n options?: never\n head?: never\n patch?: never\n trace?: never\n }\n '/v1/projects/{ref}/vanity-subdomain/check-availability': {\n parameters: {\n query?: never\n header?: never\n path?: never\n cookie?: never\n }\n get?: never\n put?: never\n /** [Beta] Checks vanity subdomain availability */\n post: operations['v1-check-vanity-subdomain-availability']\n delete?: never\n options?: never\n head?: never\n patch?: never\n trace?: never\n }\n '/v1/projects/{ref}/vanity-subdomain/activate': {\n parameters: {\n query?: never\n header?: never\n path?: never\n cookie?: never\n }\n get?: never\n put?: never\n /** [Beta] Activates a vanity subdomain for a project. */\n post: operations['v1-activate-vanity-subdomain-config']\n delete?: never\n options?: never\n head?: never\n patch?: never\n trace?: never\n }\n '/v1/projects/{ref}/upgrade': {\n parameters: {\n query?: never\n header?: never\n path?: never\n cookie?: never\n }\n get?: never\n put?: never\n /** [Beta] Upgrades the project's Postgres version */\n post: operations['v1-upgrade-postgres-version']\n delete?: never\n options?: never\n head?: never\n patch?: never\n trace?: never\n }\n '/v1/projects/{ref}/upgrade/eligibility': {\n parameters: {\n query?: never\n header?: never\n path?: never\n cookie?: never\n }\n /** [Beta] Returns the project's eligibility for upgrades */\n get: operations['v1-get-postgres-upgrade-eligibility']\n put?: never\n post?: never\n delete?: never\n options?: never\n head?: never\n patch?: never\n trace?: never\n }\n '/v1/projects/{ref}/upgrade/status': {\n parameters: {\n query?: never\n header?: never\n path?: never\n cookie?: never\n }\n /** [Beta] Gets the latest status of the project's upgrade */\n get: operations['v1-get-postgres-upgrade-status']\n put?: never\n post?: never\n delete?: never\n options?: never\n head?: never\n patch?: never\n trace?: never\n }\n '/v1/projects/{ref}/readonly': {\n parameters: {\n query?: never\n header?: never\n path?: never\n cookie?: never\n }\n /** Returns project's readonly mode status */\n get: operations['v1-get-readonly-mode-status']\n put?: never\n post?: never\n delete?: never\n options?: never\n head?: never\n patch?: never\n trace?: never\n }\n '/v1/projects/{ref}/readonly/temporary-disable': {\n parameters: {\n query?: never\n header?: never\n path?: never\n cookie?: never\n }\n get?: never\n put?: never\n /** Disables project's readonly mode for the next 15 minutes */\n post: operations['v1-disable-readonly-mode-temporarily']\n delete?: never\n options?: never\n head?: never\n patch?: never\n trace?: never\n }\n '/v1/projects/{ref}/read-replicas/setup': {\n parameters: {\n query?: never\n header?: never\n path?: never\n cookie?: never\n }\n get?: never\n put?: never\n /** [Beta] Set up a read replica */\n post: operations['v1-setup-a-read-replica']\n delete?: never\n options?: never\n head?: never\n patch?: never\n trace?: never\n }\n '/v1/projects/{ref}/read-replicas/remove': {\n parameters: {\n query?: never\n header?: never\n path?: never\n cookie?: never\n }\n get?: never\n put?: never\n /** [Beta] Remove a read replica */\n post: operations['v1-remove-a-read-replica']\n delete?: never\n options?: never\n head?: never\n patch?: never\n trace?: never\n }\n '/v1/projects/{ref}/health': {\n parameters: {\n query?: never\n header?: never\n path?: never\n cookie?: never\n }\n /** Gets project's service health status */\n get: operations['v1-get-services-health']\n put?: never\n post?: never\n delete?: never\n options?: never\n head?: never\n patch?: never\n trace?: never\n }\n '/v1/projects/{ref}/config/auth/signing-keys': {\n parameters: {\n query?: never\n header?: never\n path?: never\n cookie?: never\n }\n /** [Alpha] List all signing keys for the project */\n get: operations['v1-get-project-signing-keys']\n put?: never\n /** [Alpha] Create a new signing key for the project in standby status */\n post: operations['v1-create-project-signing-key']\n delete?: never\n options?: never\n head?: never\n patch?: never\n trace?: never\n }\n '/v1/projects/{ref}/config/auth/signing-keys/{id}': {\n parameters: {\n query?: never\n header?: never\n path?: never\n cookie?: never\n }\n /** [Alpha] Get information about a signing key */\n get: operations['v1-get-project-signing-key']\n put?: never\n post?: never\n /** [Alpha] Remove a signing key from a project, where the status is previously_used */\n delete: operations['v1-remove-project-signing-key']\n options?: never\n head?: never\n /** [Alpha] Update a signing key, mainly its status */\n patch: operations['v1-update-project-signing-key']\n trace?: never\n }\n '/v1/projects/{ref}/config/storage': {\n parameters: {\n query?: never\n header?: never\n path?: never\n cookie?: never\n }\n /** Gets project's storage config */\n get: operations['v1-get-storage-config']\n put?: never\n post?: never\n delete?: never\n options?: never\n head?: never\n /** Updates project's storage config */\n patch: operations['v1-update-storage-config']\n trace?: never\n }\n '/v1/projects/{ref}/config/database/postgres': {\n parameters: {\n query?: never\n header?: never\n path?: never\n cookie?: never\n }\n /** Gets project's Postgres config */\n get: operations['v1-get-postgres-config']\n /** Updates project's Postgres config */\n put: operations['v1-update-postgres-config']\n post?: never\n delete?: never\n options?: never\n head?: never\n patch?: never\n trace?: never\n }\n '/v1/projects/{ref}/config/database/pgbouncer': {\n parameters: {\n query?: never\n header?: never\n path?: never\n cookie?: never\n }\n /** Get project's pgbouncer config */\n get: operations['v1-get-project-pgbouncer-config']\n put?: never\n post?: never\n delete?: never\n options?: never\n head?: never\n patch?: never\n trace?: never\n }\n '/v1/projects/{ref}/config/database/pooler': {\n parameters: {\n query?: never\n header?: never\n path?: never\n cookie?: never\n }\n /** Gets project's supavisor config */\n get: operations['v1-get-pooler-config']\n put?: never\n post?: never\n delete?: never\n options?: never\n head?: never\n /** Updates project's supavisor config */\n patch: operations['v1-update-pooler-config']\n trace?: never\n }\n '/v1/projects/{ref}/config/auth': {\n parameters: {\n query?: never\n header?: never\n path?: never\n cookie?: never\n }\n /** Gets project's auth config */\n get: operations['v1-get-auth-service-config']\n put?: never\n post?: never\n delete?: never\n options?: never\n head?: never\n /** Updates a project's auth config */\n patch: operations['v1-update-auth-service-config']\n trace?: never\n }\n '/v1/projects/{ref}/config/auth/third-party-auth': {\n parameters: {\n query?: never\n header?: never\n path?: never\n cookie?: never\n }\n /** Lists all third-party auth integrations */\n get: operations['v1-list-project-tpa-integrations']\n put?: never\n /** Creates a new third-party auth integration */\n post: operations['v1-create-project-tpa-integration']\n delete?: never\n options?: never\n head?: never\n patch?: never\n trace?: never\n }\n '/v1/projects/{ref}/config/auth/third-party-auth/{tpa_id}': {\n parameters: {\n query?: never\n header?: never\n path?: never\n cookie?: never\n }\n /** Get a third-party integration */\n get: operations['v1-get-project-tpa-integration']\n put?: never\n post?: never\n /** Removes a third-party auth integration */\n delete: operations['v1-delete-project-tpa-integration']\n options?: never\n head?: never\n patch?: never\n trace?: never\n }\n '/v1/projects/{ref}/pause': {\n parameters: {\n query?: never\n header?: never\n path?: never\n cookie?: never\n }\n get?: never\n put?: never\n /** Pauses the given project */\n post: operations['v1-pause-a-project']\n delete?: never\n options?: never\n head?: never\n patch?: never\n trace?: never\n }\n '/v1/projects/{ref}/restore': {\n parameters: {\n query?: never\n header?: never\n path?: never\n cookie?: never\n }\n /** Lists available restore versions for the given project */\n get: operations['v1-list-available-restore-versions']\n put?: never\n /** Restores the given project */\n post: operations['v1-restore-a-project']\n delete?: never\n options?: never\n head?: never\n patch?: never\n trace?: never\n }\n '/v1/projects/{ref}/restore/cancel': {\n parameters: {\n query?: never\n header?: never\n path?: never\n cookie?: never\n }\n get?: never\n put?: never\n /** Cancels the given project restoration */\n post: operations['v1-cancel-a-project-restoration']\n delete?: never\n options?: never\n head?: never\n patch?: never\n trace?: never\n }\n '/v1/projects/{ref}/billing/addons': {\n parameters: {\n query?: never\n header?: never\n path?: never\n cookie?: never\n }\n /** Lists project addons */\n get: operations['v1-list-project-addons']\n put?: never\n post?: never\n delete?: never\n options?: never\n head?: never\n /** Applies project addon */\n patch: operations['v1-apply-project-addon']\n trace?: never\n }\n '/v1/projects/{ref}/billing/addons/{addon_variant}': {\n parameters: {\n query?: never\n header?: never\n path?: never\n cookie?: never\n }\n get?: never\n put?: never\n post?: never\n /** Removes project addon */\n delete: operations['v1-remove-project-addon']\n options?: never\n head?: never\n patch?: never\n trace?: never\n }\n '/v1/projects/{ref}/claim-token': {\n parameters: {\n query?: never\n header?: never\n path?: never\n cookie?: never\n }\n /** Gets project claim token */\n get: operations['v1-get-project-claim-token']\n put?: never\n /** Creates project claim token */\n post: operations['v1-create-project-claim-token']\n /** Revokes project claim token */\n delete: operations['v1-delete-project-claim-token']\n options?: never\n head?: never\n patch?: never\n trace?: never\n }\n '/v1/projects/{ref}/advisors/performance': {\n parameters: {\n query?: never\n header?: never\n path?: never\n cookie?: never\n }\n /**\n * Gets project performance advisors.\n * @deprecated\n * @description This is an **experimental** endpoint. It is subject to change or removal in future versions. Use it with caution, as it may not remain supported or stable.\n */\n get: operations['v1-get-performance-advisors']\n put?: never\n post?: never\n delete?: never\n options?: never\n head?: never\n patch?: never\n trace?: never\n }\n '/v1/projects/{ref}/advisors/security': {\n parameters: {\n query?: never\n header?: never\n path?: never\n cookie?: never\n }\n /**\n * Gets project security advisors.\n * @deprecated\n * @description This is an **experimental** endpoint. It is subject to change or removal in future versions. Use it with caution, as it may not remain supported or stable.\n */\n get: operations['v1-get-security-advisors']\n put?: never\n post?: never\n delete?: never\n options?: never\n head?: never\n patch?: never\n trace?: never\n }\n '/v1/projects/{ref}/analytics/endpoints/logs.all': {\n parameters: {\n query?: never\n header?: never\n path?: never\n cookie?: never\n }\n /**\n * Gets project's logs\n * @description Executes a SQL query on the project's logs.\n *\n * Either the 'iso_timestamp_start' and 'iso_timestamp_end' parameters must be provided.\n * If both are not provided, only the last 1 minute of logs will be queried.\n * The timestamp range must be no more than 24 hours and is rounded to the nearest minute. If the range is more than 24 hours, a validation error will be thrown.\n *\n */\n get: operations['v1-get-project-logs']\n put?: never\n post?: never\n delete?: never\n options?: never\n head?: never\n patch?: never\n trace?: never\n }\n '/v1/projects/{ref}/analytics/endpoints/usage.api-counts': {\n parameters: {\n query?: never\n header?: never\n path?: never\n cookie?: never\n }\n /** Gets project's usage api counts */\n get: operations['v1-get-project-usage-api-count']\n put?: never\n post?: never\n delete?: never\n options?: never\n head?: never\n patch?: never\n trace?: never\n }\n '/v1/projects/{ref}/analytics/endpoints/usage.api-requests-count': {\n parameters: {\n query?: never\n header?: never\n path?: never\n cookie?: never\n }\n /** Gets project's usage api requests count */\n get: operations['v1-get-project-usage-request-count']\n put?: never\n post?: never\n delete?: never\n options?: never\n head?: never\n patch?: never\n trace?: never\n }\n '/v1/projects/{ref}/database/migrations': {\n parameters: {\n query?: never\n header?: never\n path?: never\n cookie?: never\n }\n /**\n * [Beta] List applied migration versions\n * @description Only available to selected partner OAuth apps\n */\n get: operations['v1-list-migration-history']\n /**\n * [Beta] Upsert a database migration without applying\n * @description Only available to selected partner OAuth apps\n */\n put: operations['v1-upsert-a-migration']\n /**\n * [Beta] Apply a database migration\n * @description Only available to selected partner OAuth apps\n */\n post: operations['v1-apply-a-migration']\n delete?: never\n options?: never\n head?: never\n patch?: never\n trace?: never\n }\n '/v1/projects/{ref}/database/query': {\n parameters: {\n query?: never\n header?: never\n path?: never\n cookie?: never\n }\n get?: never\n put?: never\n /** [Beta] Run sql query */\n post: operations['v1-run-a-query']\n delete?: never\n options?: never\n head?: never\n patch?: never\n trace?: never\n }\n '/v1/projects/{ref}/database/webhooks/enable': {\n parameters: {\n query?: never\n header?: never\n path?: never\n cookie?: never\n }\n get?: never\n put?: never\n /** [Beta] Enables Database Webhooks on the project */\n post: operations['v1-enable-database-webhook']\n delete?: never\n options?: never\n head?: never\n patch?: never\n trace?: never\n }\n '/v1/projects/{ref}/database/context': {\n parameters: {\n query?: never\n header?: never\n path?: never\n cookie?: never\n }\n /**\n * Gets database metadata for the given project.\n * @deprecated\n * @description This is an **experimental** endpoint. It is subject to change or removal in future versions. Use it with caution, as it may not remain supported or stable.\n */\n get: operations['v1-get-database-metadata']\n put?: never\n post?: never\n delete?: never\n options?: never\n head?: never\n patch?: never\n trace?: never\n }\n '/v1/projects/{ref}/functions': {\n parameters: {\n query?: never\n header?: never\n path?: never\n cookie?: never\n }\n /**\n * List all functions\n * @description Returns all functions you've previously added to the specified project.\n */\n get: operations['v1-list-all-functions']\n /**\n * Bulk update functions\n * @description Bulk update functions. It will create a new function or replace existing. The operation is idempotent. NOTE: You will need to manually bump the version.\n */\n put: operations['v1-bulk-update-functions']\n /**\n * Create a function\n * @deprecated\n * @description This endpoint is deprecated - use the deploy endpoint. Creates a function and adds it to the specified project.\n */\n post: operations['v1-create-a-function']\n delete?: never\n options?: never\n head?: never\n patch?: never\n trace?: never\n }\n '/v1/projects/{ref}/functions/deploy': {\n parameters: {\n query?: never\n header?: never\n path?: never\n cookie?: never\n }\n get?: never\n put?: never\n /**\n * Deploy a function\n * @description A new endpoint to deploy functions. It will create if function does not exist.\n */\n post: operations['v1-deploy-a-function']\n delete?: never\n options?: never\n head?: never\n patch?: never\n trace?: never\n }\n '/v1/projects/{ref}/functions/{function_slug}': {\n parameters: {\n query?: never\n header?: never\n path?: never\n cookie?: never\n }\n /**\n * Retrieve a function\n * @description Retrieves a function with the specified slug and project.\n */\n get: operations['v1-get-a-function']\n put?: never\n post?: never\n /**\n * Delete a function\n * @description Deletes a function with the specified slug from the specified project.\n */\n delete: operations['v1-delete-a-function']\n options?: never\n head?: never\n /**\n * Update a function\n * @description Updates a function with the specified slug and project.\n */\n patch: operations['v1-update-a-function']\n trace?: never\n }\n '/v1/projects/{ref}/functions/{function_slug}/body': {\n parameters: {\n query?: never\n header?: never\n path?: never\n cookie?: never\n }\n /**\n * Retrieve a function body\n * @description Retrieves a function body for the specified slug and project.\n */\n get: operations['v1-get-a-function-body']\n put?: never\n post?: never\n delete?: never\n options?: never\n head?: never\n patch?: never\n trace?: never\n }\n '/v1/projects/{ref}/storage/buckets': {\n parameters: {\n query?: never\n header?: never\n path?: never\n cookie?: never\n }\n /** Lists all buckets */\n get: operations['v1-list-all-buckets']\n put?: never\n post?: never\n delete?: never\n options?: never\n head?: never\n patch?: never\n trace?: never\n }\n '/v1/projects/{ref}/config/auth/sso/providers': {\n parameters: {\n query?: never\n header?: never\n path?: never\n cookie?: never\n }\n /** Lists all SSO providers */\n get: operations['v1-list-all-sso-provider']\n put?: never\n /** Creates a new SSO provider */\n post: operations['v1-create-a-sso-provider']\n delete?: never\n options?: never\n head?: never\n patch?: never\n trace?: never\n }\n '/v1/projects/{ref}/config/auth/sso/providers/{provider_id}': {\n parameters: {\n query?: never\n header?: never\n path?: never\n cookie?: never\n }\n /** Gets a SSO provider by its UUID */\n get: operations['v1-get-a-sso-provider']\n /** Updates a SSO provider by its UUID */\n put: operations['v1-update-a-sso-provider']\n post?: never\n /** Removes a SSO provider by its UUID */\n delete: operations['v1-delete-a-sso-provider']\n options?: never\n head?: never\n patch?: never\n trace?: never\n }\n '/v1/projects/{ref}/database/backups': {\n parameters: {\n query?: never\n header?: never\n path?: never\n cookie?: never\n }\n /** Lists all backups */\n get: operations['v1-list-all-backups']\n put?: never\n post?: never\n delete?: never\n options?: never\n head?: never\n patch?: never\n trace?: never\n }\n '/v1/projects/{ref}/database/backups/restore-pitr': {\n parameters: {\n query?: never\n header?: never\n path?: never\n cookie?: never\n }\n get?: never\n put?: never\n /** Restores a PITR backup for a database */\n post: operations['v1-restore-pitr-backup']\n delete?: never\n options?: never\n head?: never\n patch?: never\n trace?: never\n }\n '/v1/projects/{ref}/database/backups/restore-point': {\n parameters: {\n query?: never\n header?: never\n path?: never\n cookie?: never\n }\n /** Get restore points for project */\n get: operations['v1-get-restore-point']\n put?: never\n /** Initiates a creation of a restore point for a database */\n post: operations['v1-create-restore-point']\n delete?: never\n options?: never\n head?: never\n patch?: never\n trace?: never\n }\n '/v1/projects/{ref}/database/backups/undo': {\n parameters: {\n query?: never\n header?: never\n path?: never\n cookie?: never\n }\n get?: never\n put?: never\n /** Initiates an undo to a given restore point */\n post: operations['v1-undo']\n delete?: never\n options?: never\n head?: never\n patch?: never\n trace?: never\n }\n '/v1/organizations/{slug}/members': {\n parameters: {\n query?: never\n header?: never\n path?: never\n cookie?: never\n }\n /** List members of an organization */\n get: operations['v1-list-organization-members']\n put?: never\n post?: never\n delete?: never\n options?: never\n head?: never\n patch?: never\n trace?: never\n }\n '/v1/organizations/{slug}': {\n parameters: {\n query?: never\n header?: never\n path?: never\n cookie?: never\n }\n /** Gets information about the organization */\n get: operations['v1-get-an-organization']\n put?: never\n post?: never\n delete?: never\n options?: never\n head?: never\n patch?: never\n trace?: never\n }\n '/v1/organizations/{slug}/project-claim/{token}': {\n parameters: {\n query?: never\n header?: never\n path?: never\n cookie?: never\n }\n /** Gets project details for the specified organization and claim token */\n get: operations['v1-get-organization-project-claim']\n put?: never\n /** Claims project for the specified organization */\n post: operations['v1-claim-project-for-organization']\n delete?: never\n options?: never\n head?: never\n patch?: never\n trace?: never\n }\n}\nexport type webhooks = Record\nexport interface components {\n schemas: {\n BranchDetailResponse: {\n ref: string\n postgres_version: string\n postgres_engine: string\n release_channel: string\n /** @enum {string} */\n status:\n | 'INACTIVE'\n | 'ACTIVE_HEALTHY'\n | 'ACTIVE_UNHEALTHY'\n | 'COMING_UP'\n | 'UNKNOWN'\n | 'GOING_DOWN'\n | 'INIT_FAILED'\n | 'REMOVED'\n | 'RESTORING'\n | 'UPGRADING'\n | 'PAUSING'\n | 'RESTORE_FAILED'\n | 'RESTARTING'\n | 'PAUSE_FAILED'\n | 'RESIZING'\n db_host: string\n db_port: number\n db_user?: string\n db_pass?: string\n jwt_secret?: string\n }\n UpdateBranchBody: {\n branch_name?: string\n git_branch?: string\n /**\n * @deprecated\n * @description This field is deprecated and will be ignored. Use v1-reset-a-branch endpoint directly instead.\n */\n reset_on_push?: boolean\n persistent?: boolean\n /** @enum {string} */\n status?:\n | 'CREATING_PROJECT'\n | 'RUNNING_MIGRATIONS'\n | 'MIGRATIONS_PASSED'\n | 'MIGRATIONS_FAILED'\n | 'FUNCTIONS_DEPLOYED'\n | 'FUNCTIONS_FAILED'\n }\n BranchResponse: {\n id: string\n name: string\n project_ref: string\n parent_project_ref: string\n is_default: boolean\n git_branch?: string\n /** Format: int32 */\n pr_number?: number\n /**\n * @deprecated\n * @description This field is deprecated and will not be populated.\n */\n latest_check_run_id?: number\n persistent: boolean\n /** @enum {string} */\n status:\n | 'CREATING_PROJECT'\n | 'RUNNING_MIGRATIONS'\n | 'MIGRATIONS_PASSED'\n | 'MIGRATIONS_FAILED'\n | 'FUNCTIONS_DEPLOYED'\n | 'FUNCTIONS_FAILED'\n created_at: string\n updated_at: string\n }\n BranchDeleteResponse: {\n /** @enum {string} */\n message: 'ok'\n }\n BranchActionBody: {\n migration_version?: string\n }\n BranchUpdateResponse: {\n workflow_run_id: string\n /** @enum {string} */\n message: 'ok'\n }\n V1ProjectWithDatabaseResponse: {\n /** @description Id of your project */\n id: string\n /** @description Slug of your organization */\n organization_id: string\n /** @description Name of your project */\n name: string\n /**\n * @description Region of your project\n * @example us-east-1\n */\n region: string\n /**\n * @description Creation timestamp\n * @example 2023-03-29T16:32:59Z\n */\n created_at: string\n /** @enum {string} */\n status:\n | 'INACTIVE'\n | 'ACTIVE_HEALTHY'\n | 'ACTIVE_UNHEALTHY'\n | 'COMING_UP'\n | 'UNKNOWN'\n | 'GOING_DOWN'\n | 'INIT_FAILED'\n | 'REMOVED'\n | 'RESTORING'\n | 'UPGRADING'\n | 'PAUSING'\n | 'RESTORE_FAILED'\n | 'RESTARTING'\n | 'PAUSE_FAILED'\n | 'RESIZING'\n database: {\n /** @description Database host */\n host: string\n /** @description Database version */\n version: string\n /** @description Database engine */\n postgres_engine: string\n /** @description Release channel */\n release_channel: string\n }\n }\n V1CreateProjectBody: {\n /** @description Database password */\n db_pass: string\n /** @description Name of your project */\n name: string\n /** @description Slug of your organization */\n organization_id: string\n /**\n * @deprecated\n * @description Subscription Plan is now set on organization level and is ignored in this request\n * @enum {string}\n */\n plan?: 'free' | 'pro'\n /**\n * @description Region you want your server to reside in\n * @enum {string}\n */\n region:\n | 'us-east-1'\n | 'us-east-2'\n | 'us-west-1'\n | 'us-west-2'\n | 'ap-east-1'\n | 'ap-southeast-1'\n | 'ap-northeast-1'\n | 'ap-northeast-2'\n | 'ap-southeast-2'\n | 'eu-west-1'\n | 'eu-west-2'\n | 'eu-west-3'\n | 'eu-north-1'\n | 'eu-central-1'\n | 'eu-central-2'\n | 'ca-central-1'\n | 'ap-south-1'\n | 'sa-east-1'\n /**\n * @deprecated\n * @description This field is deprecated and is ignored in this request\n */\n kps_enabled?: boolean\n /** @enum {string} */\n desired_instance_size?:\n | 'pico'\n | 'nano'\n | 'micro'\n | 'small'\n | 'medium'\n | 'large'\n | 'xlarge'\n | '2xlarge'\n | '4xlarge'\n | '8xlarge'\n | '12xlarge'\n | '16xlarge'\n | '24xlarge'\n | '24xlarge_optimized_memory'\n | '24xlarge_optimized_cpu'\n | '24xlarge_high_memory'\n | '48xlarge'\n | '48xlarge_optimized_memory'\n | '48xlarge_optimized_cpu'\n | '48xlarge_high_memory'\n /**\n * Format: uri\n * @description Template URL used to create the project from the CLI.\n * @example https://github.com/supabase/supabase/tree/master/examples/slack-clone/nextjs-slack-clone\n */\n template_url?: string\n }\n V1ProjectResponse: {\n /** @description Id of your project */\n id: string\n /** @description Slug of your organization */\n organization_id: string\n /** @description Name of your project */\n name: string\n /**\n * @description Region of your project\n * @example us-east-1\n */\n region: string\n /**\n * @description Creation timestamp\n * @example 2023-03-29T16:32:59Z\n */\n created_at: string\n /** @enum {string} */\n status:\n | 'INACTIVE'\n | 'ACTIVE_HEALTHY'\n | 'ACTIVE_UNHEALTHY'\n | 'COMING_UP'\n | 'UNKNOWN'\n | 'GOING_DOWN'\n | 'INIT_FAILED'\n | 'REMOVED'\n | 'RESTORING'\n | 'UPGRADING'\n | 'PAUSING'\n | 'RESTORE_FAILED'\n | 'RESTARTING'\n | 'PAUSE_FAILED'\n | 'RESIZING'\n }\n OrganizationResponseV1: {\n id: string\n name: string\n }\n CreateOrganizationV1: {\n name: string\n }\n OAuthTokenBody: {\n /** @enum {string} */\n grant_type?: 'authorization_code' | 'refresh_token'\n /** Format: uuid */\n client_id?: string\n client_secret?: string\n code?: string\n code_verifier?: string\n redirect_uri?: string\n refresh_token?: string\n }\n OAuthTokenResponse: {\n access_token: string\n refresh_token: string\n expires_in: number\n /** @enum {string} */\n token_type: 'Bearer'\n }\n OAuthRevokeTokenBody: {\n /** Format: uuid */\n client_id: string\n client_secret: string\n refresh_token: string\n }\n SnippetList: {\n data: {\n id: string\n inserted_at: string\n updated_at: string\n /** @enum {string} */\n type: 'sql'\n /** @enum {string} */\n visibility: 'user' | 'project' | 'org' | 'public'\n name: string\n description: string | null\n project: {\n id: number\n name: string\n }\n owner: {\n id: number\n username: string\n }\n updated_by: {\n id: number\n username: string\n }\n }[]\n cursor?: string\n }\n SnippetResponse: {\n id: string\n inserted_at: string\n updated_at: string\n /** @enum {string} */\n type: 'sql'\n /** @enum {string} */\n visibility: 'user' | 'project' | 'org' | 'public'\n name: string\n description: string | null\n project: {\n id: number\n name: string\n }\n owner: {\n id: number\n username: string\n }\n updated_by: {\n id: number\n username: string\n }\n content: {\n favorite: boolean\n schema_version: string\n sql: string\n }\n }\n ApiKeyResponse: {\n api_key: string\n id?: string | null\n /** @enum {string|null} */\n type?: 'publishable' | 'secret' | 'legacy' | null\n prefix?: string | null\n name: string\n description?: string | null\n hash?: string | null\n secret_jwt_template?: {\n role: string\n } | null\n /** Format: date-time */\n inserted_at?: string | null\n /** Format: date-time */\n updated_at?: string | null\n }\n LegacyApiKeysResponse: {\n enabled: boolean\n }\n CreateApiKeyBody: {\n /** @enum {string} */\n type: 'publishable' | 'secret'\n name: string\n description?: string | null\n secret_jwt_template?: {\n role: string\n } | null\n }\n UpdateApiKeyBody: {\n name?: string\n description?: string | null\n secret_jwt_template?: {\n role: string\n } | null\n }\n CreateBranchBody: {\n branch_name: string\n git_branch?: string\n persistent?: boolean\n region?: string\n /** @enum {string} */\n desired_instance_size?:\n | 'pico'\n | 'nano'\n | 'micro'\n | 'small'\n | 'medium'\n | 'large'\n | 'xlarge'\n | '2xlarge'\n | '4xlarge'\n | '8xlarge'\n | '12xlarge'\n | '16xlarge'\n | '24xlarge'\n | '24xlarge_optimized_memory'\n | '24xlarge_optimized_cpu'\n | '24xlarge_high_memory'\n | '48xlarge'\n | '48xlarge_optimized_memory'\n | '48xlarge_optimized_cpu'\n | '48xlarge_high_memory'\n /**\n * @description Release channel. If not provided, GA will be used.\n * @enum {string}\n */\n release_channel?: 'internal' | 'alpha' | 'beta' | 'ga' | 'withdrawn' | 'preview'\n /**\n * @description Postgres engine version. If not provided, the latest version will be used.\n * @enum {string}\n */\n postgres_engine?: '15' | '17' | '17-oriole'\n secrets?: {\n [key: string]: string\n }\n }\n UpdateCustomHostnameResponse: {\n /** @enum {string} */\n status:\n | '1_not_started'\n | '2_initiated'\n | '3_challenge_verified'\n | '4_origin_setup_completed'\n | '5_services_reconfigured'\n custom_hostname: string\n data: {\n success: boolean\n errors: unknown[]\n messages: unknown[]\n result: {\n id: string\n hostname: string\n ssl: {\n status: string\n validation_records: {\n txt_name: string\n txt_value: string\n }[]\n validation_errors?: {\n message: string\n }[]\n }\n ownership_verification: {\n type: string\n name: string\n value: string\n }\n custom_origin_server: string\n verification_errors?: string[]\n status: string\n }\n }\n }\n UpdateCustomHostnameBody: {\n custom_hostname: string\n }\n NetworkBanResponse: {\n banned_ipv4_addresses: string[]\n }\n NetworkBanResponseEnriched: {\n banned_ipv4_addresses: {\n banned_address: string\n identifier: string\n type: string\n }[]\n }\n RemoveNetworkBanRequest: {\n ipv4_addresses: string[]\n identifier?: string\n }\n NetworkRestrictionsResponse: {\n /** @enum {string} */\n entitlement: 'disallowed' | 'allowed'\n /** @description At any given point in time, this is the config that the user has requested be applied to their project. The `status` field indicates if it has been applied to the project, or is pending. When an updated config is received, the applied config is moved to `old_config`. */\n config: {\n dbAllowedCidrs?: string[]\n dbAllowedCidrsV6?: string[]\n }\n /** @description Populated when a new config has been received, but not registered as successfully applied to a project. */\n old_config?: {\n dbAllowedCidrs?: string[]\n dbAllowedCidrsV6?: string[]\n }\n /** @enum {string} */\n status: 'stored' | 'applied'\n }\n NetworkRestrictionsRequest: {\n dbAllowedCidrs?: string[]\n dbAllowedCidrsV6?: string[]\n }\n PgsodiumConfigResponse: {\n root_key: string\n }\n UpdatePgsodiumConfigBody: {\n root_key: string\n }\n PostgrestConfigWithJWTSecretResponse: {\n db_schema: string\n max_rows: number\n db_extra_search_path: string\n /** @description If `null`, the value is automatically configured based on compute size. */\n db_pool: number | null\n jwt_secret?: string\n }\n V1UpdatePostgrestConfigBody: {\n db_extra_search_path?: string\n db_schema?: string\n max_rows?: number\n db_pool?: number\n }\n V1PostgrestConfigResponse: {\n db_schema: string\n max_rows: number\n db_extra_search_path: string\n /** @description If `null`, the value is automatically configured based on compute size. */\n db_pool: number | null\n }\n V1ProjectRefResponse: {\n id: number\n ref: string\n name: string\n }\n SecretResponse: {\n name: string\n value: string\n updated_at?: string\n }\n CreateSecretBody: {\n /**\n * @description Secret name must not start with the SUPABASE_ prefix.\n * @example string\n */\n name: string\n value: string\n }[]\n SslEnforcementResponse: {\n currentConfig: {\n database: boolean\n }\n appliedSuccessfully: boolean\n }\n SslEnforcementRequest: {\n requestedConfig: {\n database: boolean\n }\n }\n TypescriptResponse: {\n types: string\n }\n VanitySubdomainConfigResponse: {\n /** @enum {string} */\n status: 'not-used' | 'custom-domain-used' | 'active'\n custom_domain?: string\n }\n VanitySubdomainBody: {\n vanity_subdomain: string\n }\n SubdomainAvailabilityResponse: {\n available: boolean\n }\n ActivateVanitySubdomainResponse: {\n custom_domain: string\n }\n UpgradeDatabaseBody: {\n target_version: string\n /** @enum {string} */\n release_channel?: 'internal' | 'alpha' | 'beta' | 'ga' | 'withdrawn' | 'preview'\n }\n ProjectUpgradeInitiateResponse: {\n tracking_id: string\n }\n ProjectUpgradeEligibilityResponse: {\n eligible: boolean\n current_app_version: string\n /** @enum {string} */\n current_app_version_release_channel:\n | 'internal'\n | 'alpha'\n | 'beta'\n | 'ga'\n | 'withdrawn'\n | 'preview'\n latest_app_version: string\n target_upgrade_versions: {\n /** @enum {string} */\n postgres_version: '15' | '17' | '17-oriole'\n /** @enum {string} */\n release_channel: 'internal' | 'alpha' | 'beta' | 'ga' | 'withdrawn' | 'preview'\n app_version: string\n }[]\n potential_breaking_changes: string[]\n duration_estimate_hours: number\n legacy_auth_custom_roles: string[]\n extension_dependent_objects: string[]\n }\n DatabaseUpgradeStatusResponse: {\n databaseUpgradeStatus: {\n initiated_at: string\n latest_status_at: string\n target_version: number\n /** @enum {string} */\n error?:\n | '1_upgraded_instance_launch_failed'\n | '2_volume_detachchment_from_upgraded_instance_failed'\n | '3_volume_attachment_to_original_instance_failed'\n | '4_data_upgrade_initiation_failed'\n | '5_data_upgrade_completion_failed'\n | '6_volume_detachchment_from_original_instance_failed'\n | '7_volume_attachment_to_upgraded_instance_failed'\n | '8_upgrade_completion_failed'\n | '9_post_physical_backup_failed'\n /** @enum {string} */\n progress?:\n | '0_requested'\n | '1_started'\n | '2_launched_upgraded_instance'\n | '3_detached_volume_from_upgraded_instance'\n | '4_attached_volume_to_original_instance'\n | '5_initiated_data_upgrade'\n | '6_completed_data_upgrade'\n | '7_detached_volume_from_original_instance'\n | '8_attached_volume_to_upgraded_instance'\n | '9_completed_upgrade'\n | '10_completed_post_physical_backup'\n status: number\n } | null\n }\n ReadOnlyStatusResponse: {\n enabled: boolean\n override_enabled: boolean\n override_active_until: string\n }\n SetUpReadReplicaBody: {\n /**\n * @description Region you want your read replica to reside in\n * @example us-east-1\n * @enum {string}\n */\n read_replica_region:\n | 'us-east-1'\n | 'us-east-2'\n | 'us-west-1'\n | 'us-west-2'\n | 'ap-east-1'\n | 'ap-southeast-1'\n | 'ap-northeast-1'\n | 'ap-northeast-2'\n | 'ap-southeast-2'\n | 'eu-west-1'\n | 'eu-west-2'\n | 'eu-west-3'\n | 'eu-north-1'\n | 'eu-central-1'\n | 'eu-central-2'\n | 'ca-central-1'\n | 'ap-south-1'\n | 'sa-east-1'\n }\n RemoveReadReplicaBody: {\n database_identifier: string\n }\n V1ServiceHealthResponse: {\n /** @enum {string} */\n name: 'auth' | 'db' | 'pooler' | 'realtime' | 'rest' | 'storage'\n healthy: boolean\n /** @enum {string} */\n status: 'COMING_UP' | 'ACTIVE_HEALTHY' | 'UNHEALTHY'\n info?:\n | {\n /** @enum {string} */\n name: 'GoTrue'\n version: string\n description: string\n }\n | {\n healthy: boolean\n db_connected: boolean\n connected_cluster: number\n }\n error?: string\n }\n CreateSigningKeyBody: {\n /** @enum {string} */\n algorithm: 'EdDSA' | 'ES256' | 'RS256' | 'HS256'\n /** @enum {string} */\n status?: 'in_use' | 'standby'\n }\n SigningKeyResponse: {\n /** Format: uuid */\n id: string\n /** @enum {string} */\n algorithm: 'EdDSA' | 'ES256' | 'RS256' | 'HS256'\n /** @enum {string} */\n status: 'in_use' | 'previously_used' | 'revoked' | 'standby'\n public_jwk?: unknown\n /** Format: date-time */\n created_at: string\n /** Format: date-time */\n updated_at: string\n }\n SigningKeysResponse: {\n keys: {\n /** Format: uuid */\n id: string\n /** @enum {string} */\n algorithm: 'EdDSA' | 'ES256' | 'RS256' | 'HS256'\n /** @enum {string} */\n status: 'in_use' | 'previously_used' | 'revoked' | 'standby'\n public_jwk?: unknown\n /** Format: date-time */\n created_at: string\n /** Format: date-time */\n updated_at: string\n }[]\n }\n UpdateSigningKeyBody: {\n /** @enum {string} */\n status: 'in_use' | 'previously_used' | 'revoked' | 'standby'\n }\n StorageConfigResponse: {\n /** Format: int64 */\n fileSizeLimit: number\n features: {\n imageTransformation: {\n enabled: boolean\n }\n s3Protocol: {\n enabled: boolean\n }\n }\n }\n UpdateStorageConfigBody: {\n /** Format: int64 */\n fileSizeLimit?: number\n features?: {\n imageTransformation: {\n enabled: boolean\n }\n s3Protocol: {\n enabled: boolean\n }\n }\n }\n PostgresConfigResponse: {\n effective_cache_size?: string\n logical_decoding_work_mem?: string\n maintenance_work_mem?: string\n track_activity_query_size?: string\n max_connections?: number\n max_locks_per_transaction?: number\n max_parallel_maintenance_workers?: number\n max_parallel_workers?: number\n max_parallel_workers_per_gather?: number\n max_replication_slots?: number\n max_slot_wal_keep_size?: string\n max_standby_archive_delay?: string\n max_standby_streaming_delay?: string\n max_wal_size?: string\n max_wal_senders?: number\n max_worker_processes?: number\n /** @enum {string} */\n session_replication_role?: 'origin' | 'replica' | 'local'\n shared_buffers?: string\n statement_timeout?: string\n track_commit_timestamp?: boolean\n wal_keep_size?: string\n wal_sender_timeout?: string\n work_mem?: string\n }\n UpdatePostgresConfigBody: {\n effective_cache_size?: string\n logical_decoding_work_mem?: string\n maintenance_work_mem?: string\n track_activity_query_size?: string\n max_connections?: number\n max_locks_per_transaction?: number\n max_parallel_maintenance_workers?: number\n max_parallel_workers?: number\n max_parallel_workers_per_gather?: number\n max_replication_slots?: number\n max_slot_wal_keep_size?: string\n max_standby_archive_delay?: string\n max_standby_streaming_delay?: string\n max_wal_size?: string\n max_wal_senders?: number\n max_worker_processes?: number\n /** @enum {string} */\n session_replication_role?: 'origin' | 'replica' | 'local'\n shared_buffers?: string\n statement_timeout?: string\n track_commit_timestamp?: boolean\n wal_keep_size?: string\n wal_sender_timeout?: string\n work_mem?: string\n restart_database?: boolean\n }\n V1PgbouncerConfigResponse: {\n default_pool_size?: number\n ignore_startup_parameters?: string\n max_client_conn?: number\n /** @enum {string} */\n pool_mode?: 'transaction' | 'session' | 'statement'\n connection_string?: string\n }\n SupavisorConfigResponse: {\n identifier: string\n /** @enum {string} */\n database_type: 'PRIMARY' | 'READ_REPLICA'\n is_using_scram_auth: boolean\n db_user: string\n db_host: string\n db_port: number\n db_name: string\n connection_string: string\n /** @description Use connection_string instead */\n connectionString: string\n default_pool_size: number | null\n max_client_conn: number | null\n /** @enum {string} */\n pool_mode: 'transaction' | 'session'\n }\n UpdateSupavisorConfigBody: {\n default_pool_size?: number | null\n /**\n * @description Dedicated pooler mode for the project\n * @enum {string}\n */\n pool_mode?: 'transaction' | 'session'\n }\n UpdateSupavisorConfigResponse: {\n default_pool_size: number | null\n pool_mode: string\n }\n AuthConfigResponse: {\n api_max_request_duration: number | null\n db_max_pool_size: number | null\n disable_signup: boolean | null\n external_anonymous_users_enabled: boolean | null\n external_apple_additional_client_ids: string | null\n external_apple_client_id: string | null\n external_apple_enabled: boolean | null\n external_apple_secret: string | null\n external_azure_client_id: string | null\n external_azure_enabled: boolean | null\n external_azure_secret: string | null\n external_azure_url: string | null\n external_bitbucket_client_id: string | null\n external_bitbucket_enabled: boolean | null\n external_bitbucket_secret: string | null\n external_discord_client_id: string | null\n external_discord_enabled: boolean | null\n external_discord_secret: string | null\n external_email_enabled: boolean | null\n external_facebook_client_id: string | null\n external_facebook_enabled: boolean | null\n external_facebook_secret: string | null\n external_figma_client_id: string | null\n external_figma_enabled: boolean | null\n external_figma_secret: string | null\n external_github_client_id: string | null\n external_github_enabled: boolean | null\n external_github_secret: string | null\n external_gitlab_client_id: string | null\n external_gitlab_enabled: boolean | null\n external_gitlab_secret: string | null\n external_gitlab_url: string | null\n external_google_additional_client_ids: string | null\n external_google_client_id: string | null\n external_google_enabled: boolean | null\n external_google_secret: string | null\n external_google_skip_nonce_check: boolean | null\n external_kakao_client_id: string | null\n external_kakao_enabled: boolean | null\n external_kakao_secret: string | null\n external_keycloak_client_id: string | null\n external_keycloak_enabled: boolean | null\n external_keycloak_secret: string | null\n external_keycloak_url: string | null\n external_linkedin_oidc_client_id: string | null\n external_linkedin_oidc_enabled: boolean | null\n external_linkedin_oidc_secret: string | null\n external_slack_oidc_client_id: string | null\n external_slack_oidc_enabled: boolean | null\n external_slack_oidc_secret: string | null\n external_notion_client_id: string | null\n external_notion_enabled: boolean | null\n external_notion_secret: string | null\n external_phone_enabled: boolean | null\n external_slack_client_id: string | null\n external_slack_enabled: boolean | null\n external_slack_secret: string | null\n external_spotify_client_id: string | null\n external_spotify_enabled: boolean | null\n external_spotify_secret: string | null\n external_twitch_client_id: string | null\n external_twitch_enabled: boolean | null\n external_twitch_secret: string | null\n external_twitter_client_id: string | null\n external_twitter_enabled: boolean | null\n external_twitter_secret: string | null\n external_workos_client_id: string | null\n external_workos_enabled: boolean | null\n external_workos_secret: string | null\n external_workos_url: string | null\n external_web3_solana_enabled: boolean | null\n external_zoom_client_id: string | null\n external_zoom_enabled: boolean | null\n external_zoom_secret: string | null\n hook_custom_access_token_enabled: boolean | null\n hook_custom_access_token_uri: string | null\n hook_custom_access_token_secrets: string | null\n hook_mfa_verification_attempt_enabled: boolean | null\n hook_mfa_verification_attempt_uri: string | null\n hook_mfa_verification_attempt_secrets: string | null\n hook_password_verification_attempt_enabled: boolean | null\n hook_password_verification_attempt_uri: string | null\n hook_password_verification_attempt_secrets: string | null\n hook_send_sms_enabled: boolean | null\n hook_send_sms_uri: string | null\n hook_send_sms_secrets: string | null\n hook_send_email_enabled: boolean | null\n hook_send_email_uri: string | null\n hook_send_email_secrets: string | null\n hook_before_user_created_enabled: boolean | null\n hook_before_user_created_uri: string | null\n hook_before_user_created_secrets: string | null\n jwt_exp: number | null\n mailer_allow_unverified_email_sign_ins: boolean | null\n mailer_autoconfirm: boolean | null\n mailer_otp_exp: number\n mailer_otp_length: number | null\n mailer_secure_email_change_enabled: boolean | null\n mailer_subjects_confirmation: string | null\n mailer_subjects_email_change: string | null\n mailer_subjects_invite: string | null\n mailer_subjects_magic_link: string | null\n mailer_subjects_reauthentication: string | null\n mailer_subjects_recovery: string | null\n mailer_templates_confirmation_content: string | null\n mailer_templates_email_change_content: string | null\n mailer_templates_invite_content: string | null\n mailer_templates_magic_link_content: string | null\n mailer_templates_reauthentication_content: string | null\n mailer_templates_recovery_content: string | null\n mfa_max_enrolled_factors: number | null\n mfa_totp_enroll_enabled: boolean | null\n mfa_totp_verify_enabled: boolean | null\n mfa_phone_enroll_enabled: boolean | null\n mfa_phone_verify_enabled: boolean | null\n mfa_web_authn_enroll_enabled: boolean | null\n mfa_web_authn_verify_enabled: boolean | null\n mfa_phone_otp_length: number\n mfa_phone_template: string | null\n mfa_phone_max_frequency: number | null\n password_hibp_enabled: boolean | null\n password_min_length: number | null\n /** @enum {string|null} */\n password_required_characters:\n | 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ:0123456789'\n | 'abcdefghijklmnopqrstuvwxyz:ABCDEFGHIJKLMNOPQRSTUVWXYZ:0123456789'\n | 'abcdefghijklmnopqrstuvwxyz:ABCDEFGHIJKLMNOPQRSTUVWXYZ:0123456789:!@#$%^&*()_+-=[]{};\\'\\\\\\\\:\"|<>?,./`~'\n | ''\n | null\n rate_limit_anonymous_users: number | null\n rate_limit_email_sent: number | null\n rate_limit_sms_sent: number | null\n rate_limit_token_refresh: number | null\n rate_limit_verify: number | null\n rate_limit_otp: number | null\n rate_limit_web3: number | null\n refresh_token_rotation_enabled: boolean | null\n saml_enabled: boolean | null\n saml_external_url: string | null\n saml_allow_encrypted_assertions: boolean | null\n security_captcha_enabled: boolean | null\n /** @enum {string|null} */\n security_captcha_provider: 'turnstile' | 'hcaptcha' | null\n security_captcha_secret: string | null\n security_manual_linking_enabled: boolean | null\n security_refresh_token_reuse_interval: number | null\n security_update_password_require_reauthentication: boolean | null\n sessions_inactivity_timeout: number | null\n sessions_single_per_user: boolean | null\n sessions_tags: string | null\n sessions_timebox: number | null\n site_url: string | null\n sms_autoconfirm: boolean | null\n sms_max_frequency: number | null\n sms_messagebird_access_key: string | null\n sms_messagebird_originator: string | null\n sms_otp_exp: number | null\n sms_otp_length: number\n /** @enum {string|null} */\n sms_provider: 'messagebird' | 'textlocal' | 'twilio' | 'twilio_verify' | 'vonage' | null\n sms_template: string | null\n sms_test_otp: string | null\n /** Format: date-time */\n sms_test_otp_valid_until: string | null\n sms_textlocal_api_key: string | null\n sms_textlocal_sender: string | null\n sms_twilio_account_sid: string | null\n sms_twilio_auth_token: string | null\n sms_twilio_content_sid: string | null\n sms_twilio_message_service_sid: string | null\n sms_twilio_verify_account_sid: string | null\n sms_twilio_verify_auth_token: string | null\n sms_twilio_verify_message_service_sid: string | null\n sms_vonage_api_key: string | null\n sms_vonage_api_secret: string | null\n sms_vonage_from: string | null\n smtp_admin_email: string | null\n smtp_host: string | null\n smtp_max_frequency: number | null\n smtp_pass: string | null\n smtp_port: string | null\n smtp_sender_name: string | null\n smtp_user: string | null\n uri_allow_list: string | null\n }\n UpdateAuthConfigBody: {\n site_url?: string | null\n disable_signup?: boolean | null\n jwt_exp?: number | null\n smtp_admin_email?: string | null\n smtp_host?: string | null\n smtp_port?: string | null\n smtp_user?: string | null\n smtp_pass?: string | null\n smtp_max_frequency?: number | null\n smtp_sender_name?: string | null\n mailer_allow_unverified_email_sign_ins?: boolean | null\n mailer_autoconfirm?: boolean | null\n mailer_subjects_invite?: string | null\n mailer_subjects_confirmation?: string | null\n mailer_subjects_recovery?: string | null\n mailer_subjects_email_change?: string | null\n mailer_subjects_magic_link?: string | null\n mailer_subjects_reauthentication?: string | null\n mailer_templates_invite_content?: string | null\n mailer_templates_confirmation_content?: string | null\n mailer_templates_recovery_content?: string | null\n mailer_templates_email_change_content?: string | null\n mailer_templates_magic_link_content?: string | null\n mailer_templates_reauthentication_content?: string | null\n mfa_max_enrolled_factors?: number | null\n uri_allow_list?: string | null\n external_anonymous_users_enabled?: boolean | null\n external_email_enabled?: boolean | null\n external_phone_enabled?: boolean | null\n saml_enabled?: boolean | null\n saml_external_url?: string | null\n security_captcha_enabled?: boolean | null\n /** @enum {string|null} */\n security_captcha_provider?: 'turnstile' | 'hcaptcha' | null\n security_captcha_secret?: string | null\n sessions_timebox?: number | null\n sessions_inactivity_timeout?: number | null\n sessions_single_per_user?: boolean | null\n sessions_tags?: string | null\n rate_limit_anonymous_users?: number | null\n rate_limit_email_sent?: number | null\n rate_limit_sms_sent?: number | null\n rate_limit_verify?: number | null\n rate_limit_token_refresh?: number | null\n rate_limit_otp?: number | null\n rate_limit_web3?: number | null\n mailer_secure_email_change_enabled?: boolean | null\n refresh_token_rotation_enabled?: boolean | null\n password_hibp_enabled?: boolean | null\n password_min_length?: number | null\n /** @enum {string|null} */\n password_required_characters?:\n | 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ:0123456789'\n | 'abcdefghijklmnopqrstuvwxyz:ABCDEFGHIJKLMNOPQRSTUVWXYZ:0123456789'\n | 'abcdefghijklmnopqrstuvwxyz:ABCDEFGHIJKLMNOPQRSTUVWXYZ:0123456789:!@#$%^&*()_+-=[]{};\\'\\\\\\\\:\"|<>?,./`~'\n | ''\n | null\n security_manual_linking_enabled?: boolean | null\n security_update_password_require_reauthentication?: boolean | null\n security_refresh_token_reuse_interval?: number | null\n mailer_otp_exp?: number\n mailer_otp_length?: number | null\n sms_autoconfirm?: boolean | null\n sms_max_frequency?: number | null\n sms_otp_exp?: number | null\n sms_otp_length?: number\n /** @enum {string|null} */\n sms_provider?: 'messagebird' | 'textlocal' | 'twilio' | 'twilio_verify' | 'vonage' | null\n sms_messagebird_access_key?: string | null\n sms_messagebird_originator?: string | null\n sms_test_otp?: string | null\n /** Format: date-time */\n sms_test_otp_valid_until?: string | null\n sms_textlocal_api_key?: string | null\n sms_textlocal_sender?: string | null\n sms_twilio_account_sid?: string | null\n sms_twilio_auth_token?: string | null\n sms_twilio_content_sid?: string | null\n sms_twilio_message_service_sid?: string | null\n sms_twilio_verify_account_sid?: string | null\n sms_twilio_verify_auth_token?: string | null\n sms_twilio_verify_message_service_sid?: string | null\n sms_vonage_api_key?: string | null\n sms_vonage_api_secret?: string | null\n sms_vonage_from?: string | null\n sms_template?: string | null\n hook_mfa_verification_attempt_enabled?: boolean | null\n hook_mfa_verification_attempt_uri?: string | null\n hook_mfa_verification_attempt_secrets?: string | null\n hook_password_verification_attempt_enabled?: boolean | null\n hook_password_verification_attempt_uri?: string | null\n hook_password_verification_attempt_secrets?: string | null\n hook_custom_access_token_enabled?: boolean | null\n hook_custom_access_token_uri?: string | null\n hook_custom_access_token_secrets?: string | null\n hook_send_sms_enabled?: boolean | null\n hook_send_sms_uri?: string | null\n hook_send_sms_secrets?: string | null\n hook_send_email_enabled?: boolean | null\n hook_send_email_uri?: string | null\n hook_send_email_secrets?: string | null\n hook_before_user_created_enabled?: boolean | null\n hook_before_user_created_uri?: string | null\n hook_before_user_created_secrets?: string | null\n external_apple_enabled?: boolean | null\n external_apple_client_id?: string | null\n external_apple_secret?: string | null\n external_apple_additional_client_ids?: string | null\n external_azure_enabled?: boolean | null\n external_azure_client_id?: string | null\n external_azure_secret?: string | null\n external_azure_url?: string | null\n external_bitbucket_enabled?: boolean | null\n external_bitbucket_client_id?: string | null\n external_bitbucket_secret?: string | null\n external_discord_enabled?: boolean | null\n external_discord_client_id?: string | null\n external_discord_secret?: string | null\n external_facebook_enabled?: boolean | null\n external_facebook_client_id?: string | null\n external_facebook_secret?: string | null\n external_figma_enabled?: boolean | null\n external_figma_client_id?: string | null\n external_figma_secret?: string | null\n external_github_enabled?: boolean | null\n external_github_client_id?: string | null\n external_github_secret?: string | null\n external_gitlab_enabled?: boolean | null\n external_gitlab_client_id?: string | null\n external_gitlab_secret?: string | null\n external_gitlab_url?: string | null\n external_google_enabled?: boolean | null\n external_google_client_id?: string | null\n external_google_secret?: string | null\n external_google_additional_client_ids?: string | null\n external_google_skip_nonce_check?: boolean | null\n external_kakao_enabled?: boolean | null\n external_kakao_client_id?: string | null\n external_kakao_secret?: string | null\n external_keycloak_enabled?: boolean | null\n external_keycloak_client_id?: string | null\n external_keycloak_secret?: string | null\n external_keycloak_url?: string | null\n external_linkedin_oidc_enabled?: boolean | null\n external_linkedin_oidc_client_id?: string | null\n external_linkedin_oidc_secret?: string | null\n external_slack_oidc_enabled?: boolean | null\n external_slack_oidc_client_id?: string | null\n external_slack_oidc_secret?: string | null\n external_notion_enabled?: boolean | null\n external_notion_client_id?: string | null\n external_notion_secret?: string | null\n external_slack_enabled?: boolean | null\n external_slack_client_id?: string | null\n external_slack_secret?: string | null\n external_spotify_enabled?: boolean | null\n external_spotify_client_id?: string | null\n external_spotify_secret?: string | null\n external_twitch_enabled?: boolean | null\n external_twitch_client_id?: string | null\n external_twitch_secret?: string | null\n external_twitter_enabled?: boolean | null\n external_twitter_client_id?: string | null\n external_twitter_secret?: string | null\n external_workos_enabled?: boolean | null\n external_workos_client_id?: string | null\n external_workos_secret?: string | null\n external_workos_url?: string | null\n external_web3_solana_enabled?: boolean | null\n external_zoom_enabled?: boolean | null\n external_zoom_client_id?: string | null\n external_zoom_secret?: string | null\n db_max_pool_size?: number | null\n api_max_request_duration?: number | null\n mfa_totp_enroll_enabled?: boolean | null\n mfa_totp_verify_enabled?: boolean | null\n mfa_web_authn_enroll_enabled?: boolean | null\n mfa_web_authn_verify_enabled?: boolean | null\n mfa_phone_enroll_enabled?: boolean | null\n mfa_phone_verify_enabled?: boolean | null\n mfa_phone_max_frequency?: number | null\n mfa_phone_otp_length?: number | null\n mfa_phone_template?: string | null\n }\n CreateThirdPartyAuthBody: {\n oidc_issuer_url?: string\n jwks_url?: string\n custom_jwks?: unknown\n }\n ThirdPartyAuth: {\n /** Format: uuid */\n id: string\n type: string\n oidc_issuer_url?: string | null\n jwks_url?: string | null\n custom_jwks?: unknown\n resolved_jwks?: unknown\n inserted_at: string\n updated_at: string\n resolved_at?: string | null\n }\n GetProjectAvailableRestoreVersionsResponse: {\n available_versions: {\n version: string\n /** @enum {string} */\n release_channel: 'internal' | 'alpha' | 'beta' | 'ga' | 'withdrawn' | 'preview'\n /** @enum {string} */\n postgres_engine: '13' | '14' | '15' | '17' | '17-oriole'\n }[]\n }\n ListProjectAddonsResponse: {\n selected_addons: {\n /** @enum {string} */\n type:\n | 'custom_domain'\n | 'compute_instance'\n | 'pitr'\n | 'ipv4'\n | 'auth_mfa_phone'\n | 'auth_mfa_web_authn'\n | 'log_drain'\n variant: {\n id:\n | (\n | 'ci_micro'\n | 'ci_small'\n | 'ci_medium'\n | 'ci_large'\n | 'ci_xlarge'\n | 'ci_2xlarge'\n | 'ci_4xlarge'\n | 'ci_8xlarge'\n | 'ci_12xlarge'\n | 'ci_16xlarge'\n | 'ci_24xlarge'\n | 'ci_24xlarge_optimized_cpu'\n | 'ci_24xlarge_optimized_memory'\n | 'ci_24xlarge_high_memory'\n | 'ci_48xlarge'\n | 'ci_48xlarge_optimized_cpu'\n | 'ci_48xlarge_optimized_memory'\n | 'ci_48xlarge_high_memory'\n )\n | 'cd_default'\n | ('pitr_7' | 'pitr_14' | 'pitr_28')\n | 'ipv4_default'\n | 'auth_mfa_phone_default'\n | 'auth_mfa_web_authn_default'\n | 'log_drain_default'\n name: string\n price: {\n description: string\n /** @enum {string} */\n type: 'fixed' | 'usage'\n /** @enum {string} */\n interval: 'monthly' | 'hourly'\n amount: number\n }\n /** @description Any JSON-serializable value */\n meta?: unknown\n }\n }[]\n available_addons: {\n /** @enum {string} */\n type:\n | 'custom_domain'\n | 'compute_instance'\n | 'pitr'\n | 'ipv4'\n | 'auth_mfa_phone'\n | 'auth_mfa_web_authn'\n | 'log_drain'\n name: string\n variants: {\n id:\n | (\n | 'ci_micro'\n | 'ci_small'\n | 'ci_medium'\n | 'ci_large'\n | 'ci_xlarge'\n | 'ci_2xlarge'\n | 'ci_4xlarge'\n | 'ci_8xlarge'\n | 'ci_12xlarge'\n | 'ci_16xlarge'\n | 'ci_24xlarge'\n | 'ci_24xlarge_optimized_cpu'\n | 'ci_24xlarge_optimized_memory'\n | 'ci_24xlarge_high_memory'\n | 'ci_48xlarge'\n | 'ci_48xlarge_optimized_cpu'\n | 'ci_48xlarge_optimized_memory'\n | 'ci_48xlarge_high_memory'\n )\n | 'cd_default'\n | ('pitr_7' | 'pitr_14' | 'pitr_28')\n | 'ipv4_default'\n | 'auth_mfa_phone_default'\n | 'auth_mfa_web_authn_default'\n | 'log_drain_default'\n name: string\n price: {\n description: string\n /** @enum {string} */\n type: 'fixed' | 'usage'\n /** @enum {string} */\n interval: 'monthly' | 'hourly'\n amount: number\n }\n /** @description Any JSON-serializable value */\n meta?: unknown\n }[]\n }[]\n }\n ApplyProjectAddonBody: {\n addon_variant:\n | (\n | 'ci_micro'\n | 'ci_small'\n | 'ci_medium'\n | 'ci_large'\n | 'ci_xlarge'\n | 'ci_2xlarge'\n | 'ci_4xlarge'\n | 'ci_8xlarge'\n | 'ci_12xlarge'\n | 'ci_16xlarge'\n | 'ci_24xlarge'\n | 'ci_24xlarge_optimized_cpu'\n | 'ci_24xlarge_optimized_memory'\n | 'ci_24xlarge_high_memory'\n | 'ci_48xlarge'\n | 'ci_48xlarge_optimized_cpu'\n | 'ci_48xlarge_optimized_memory'\n | 'ci_48xlarge_high_memory'\n )\n | 'cd_default'\n | ('pitr_7' | 'pitr_14' | 'pitr_28')\n | 'ipv4_default'\n /** @enum {string} */\n addon_type:\n | 'custom_domain'\n | 'compute_instance'\n | 'pitr'\n | 'ipv4'\n | 'auth_mfa_phone'\n | 'auth_mfa_web_authn'\n | 'log_drain'\n }\n ProjectClaimTokenResponse: {\n token_alias: string\n expires_at: string\n created_at: string\n /** Format: uuid */\n created_by: string\n }\n CreateProjectClaimTokenResponse: {\n token: string\n token_alias: string\n expires_at: string\n created_at: string\n /** Format: uuid */\n created_by: string\n }\n V1ProjectAdvisorsResponse: {\n lints: {\n /** @enum {string} */\n name:\n | 'unindexed_foreign_keys'\n | 'auth_users_exposed'\n | 'auth_rls_initplan'\n | 'no_primary_key'\n | 'unused_index'\n | 'multiple_permissive_policies'\n | 'policy_exists_rls_disabled'\n | 'rls_enabled_no_policy'\n | 'duplicate_index'\n | 'security_definer_view'\n | 'function_search_path_mutable'\n | 'rls_disabled_in_public'\n | 'extension_in_public'\n | 'rls_references_user_metadata'\n | 'materialized_view_in_api'\n | 'foreign_table_in_api'\n | 'unsupported_reg_types'\n | 'auth_otp_long_expiry'\n | 'auth_otp_short_length'\n | 'ssl_not_enforced'\n | 'network_restrictions_not_set'\n | 'password_requirements_min_length'\n | 'pitr_not_enabled'\n | 'auth_leaked_password_protection'\n | 'auth_insufficient_mfa_options'\n | 'auth_password_policy_missing'\n | 'leaked_service_key'\n | 'no_backup_admin'\n title: string\n /** @enum {string} */\n level: 'ERROR' | 'WARN' | 'INFO'\n /** @enum {string} */\n facing: 'EXTERNAL'\n categories: ('PERFORMANCE' | 'SECURITY')[]\n description: string\n detail: string\n remediation: string\n metadata?: {\n schema?: string\n name?: string\n entity?: string\n /** @enum {string} */\n type?: 'table' | 'view' | 'auth' | 'function' | 'extension' | 'compliance'\n fkey_name?: string\n fkey_columns?: number[]\n }\n cache_key: string\n }[]\n }\n AnalyticsResponse: {\n result?: unknown[]\n error?:\n | string\n | {\n code: number\n errors: {\n domain: string\n location: string\n locationType: string\n message: string\n reason: string\n }[]\n message: string\n status: string\n }\n }\n V1ListMigrationsResponse: {\n version: string\n name?: string\n }[]\n V1CreateMigrationBody: {\n query: string\n name?: string\n }\n V1UpsertMigrationBody: {\n query: string\n name?: string\n }\n V1RunQueryBody: {\n query: string\n read_only?: boolean\n }\n GetProjectDbMetadataResponse: {\n databases: ({\n name: string\n schemas: ({\n name: string\n } & {\n [key: string]: unknown\n })[]\n } & {\n [key: string]: unknown\n })[]\n }\n FunctionResponse: {\n id: string\n slug: string\n name: string\n /** @enum {string} */\n status: 'ACTIVE' | 'REMOVED' | 'THROTTLED'\n version: number\n /** Format: int64 */\n created_at: number\n /** Format: int64 */\n updated_at: number\n verify_jwt?: boolean\n import_map?: boolean\n entrypoint_path?: string\n import_map_path?: string\n }\n V1CreateFunctionBody: {\n slug: string\n name: string\n body: string\n verify_jwt?: boolean\n }\n BulkUpdateFunctionBody: {\n id: string\n slug: string\n name: string\n /** @enum {string} */\n status: 'ACTIVE' | 'REMOVED' | 'THROTTLED'\n version: number\n /** Format: int64 */\n created_at?: number\n verify_jwt?: boolean\n import_map?: boolean\n entrypoint_path?: string\n import_map_path?: string\n }[]\n BulkUpdateFunctionResponse: {\n functions: {\n id: string\n slug: string\n name: string\n /** @enum {string} */\n status: 'ACTIVE' | 'REMOVED' | 'THROTTLED'\n version: number\n /** Format: int64 */\n created_at: number\n /** Format: int64 */\n updated_at: number\n verify_jwt?: boolean\n import_map?: boolean\n entrypoint_path?: string\n import_map_path?: string\n }[]\n }\n FunctionDeployBody: {\n file?: string[]\n metadata: {\n entrypoint_path: string\n import_map_path?: string\n static_patterns?: string[]\n verify_jwt?: boolean\n name?: string\n }\n }\n DeployFunctionResponse: {\n id: string\n slug: string\n name: string\n /** @enum {string} */\n status: 'ACTIVE' | 'REMOVED' | 'THROTTLED'\n version: number\n /** Format: int64 */\n created_at?: number\n /** Format: int64 */\n updated_at?: number\n verify_jwt?: boolean\n import_map?: boolean\n entrypoint_path?: string\n import_map_path?: string\n }\n FunctionSlugResponse: {\n id: string\n slug: string\n name: string\n /** @enum {string} */\n status: 'ACTIVE' | 'REMOVED' | 'THROTTLED'\n version: number\n /** Format: int64 */\n created_at: number\n /** Format: int64 */\n updated_at: number\n verify_jwt?: boolean\n import_map?: boolean\n entrypoint_path?: string\n import_map_path?: string\n }\n StreamableFile: Record\n V1UpdateFunctionBody: {\n name?: string\n body?: string\n verify_jwt?: boolean\n }\n V1StorageBucketResponse: {\n id: string\n name: string\n owner: string\n created_at: string\n updated_at: string\n public: boolean\n }\n CreateProviderBody: {\n /**\n * @description What type of provider will be created\n * @enum {string}\n */\n type: 'saml'\n metadata_xml?: string\n metadata_url?: string\n domains?: string[]\n attribute_mapping?: {\n keys: {\n [key: string]: {\n name?: string\n names?: string[]\n default?: Record | number | string | boolean\n array?: boolean\n }\n }\n }\n }\n CreateProviderResponse: {\n id: string\n saml?: {\n id: string\n entity_id: string\n metadata_url?: string\n metadata_xml?: string\n attribute_mapping?: {\n keys: {\n [key: string]: {\n name?: string\n names?: string[]\n default?: Record | number | string | boolean\n array?: boolean\n }\n }\n }\n }\n domains?: {\n id: string\n domain?: string\n created_at?: string\n updated_at?: string\n }[]\n created_at?: string\n updated_at?: string\n }\n ListProvidersResponse: {\n items: {\n id: string\n saml?: {\n id: string\n entity_id: string\n metadata_url?: string\n metadata_xml?: string\n attribute_mapping?: {\n keys: {\n [key: string]: {\n name?: string\n names?: string[]\n default?: Record | number | string | boolean\n array?: boolean\n }\n }\n }\n }\n domains?: {\n id: string\n domain?: string\n created_at?: string\n updated_at?: string\n }[]\n created_at?: string\n updated_at?: string\n }[]\n }\n GetProviderResponse: {\n id: string\n saml?: {\n id: string\n entity_id: string\n metadata_url?: string\n metadata_xml?: string\n attribute_mapping?: {\n keys: {\n [key: string]: {\n name?: string\n names?: string[]\n default?: Record | number | string | boolean\n array?: boolean\n }\n }\n }\n }\n domains?: {\n id: string\n domain?: string\n created_at?: string\n updated_at?: string\n }[]\n created_at?: string\n updated_at?: string\n }\n UpdateProviderBody: {\n metadata_xml?: string\n metadata_url?: string\n domains?: string[]\n attribute_mapping?: {\n keys: {\n [key: string]: {\n name?: string\n names?: string[]\n default?: Record | number | string | boolean\n array?: boolean\n }\n }\n }\n }\n UpdateProviderResponse: {\n id: string\n saml?: {\n id: string\n entity_id: string\n metadata_url?: string\n metadata_xml?: string\n attribute_mapping?: {\n keys: {\n [key: string]: {\n name?: string\n names?: string[]\n default?: Record | number | string | boolean\n array?: boolean\n }\n }\n }\n }\n domains?: {\n id: string\n domain?: string\n created_at?: string\n updated_at?: string\n }[]\n created_at?: string\n updated_at?: string\n }\n DeleteProviderResponse: {\n id: string\n saml?: {\n id: string\n entity_id: string\n metadata_url?: string\n metadata_xml?: string\n attribute_mapping?: {\n keys: {\n [key: string]: {\n name?: string\n names?: string[]\n default?: Record | number | string | boolean\n array?: boolean\n }\n }\n }\n }\n domains?: {\n id: string\n domain?: string\n created_at?: string\n updated_at?: string\n }[]\n created_at?: string\n updated_at?: string\n }\n V1BackupsResponse: {\n region: string\n walg_enabled: boolean\n pitr_enabled: boolean\n backups: {\n is_physical_backup: boolean\n /** @enum {string} */\n status: 'COMPLETED' | 'FAILED' | 'PENDING' | 'REMOVED' | 'ARCHIVED' | 'CANCELLED'\n inserted_at: string\n }[]\n physical_backup_data: {\n earliest_physical_backup_date_unix?: number\n latest_physical_backup_date_unix?: number\n }\n }\n V1RestorePitrBody: {\n /** Format: int64 */\n recovery_time_target_unix: number\n }\n V1RestorePointPostBody: {\n name: string\n }\n V1RestorePointResponse: {\n name: string\n /** @enum {string} */\n status: 'AVAILABLE' | 'PENDING' | 'REMOVED'\n }\n V1UndoBody: {\n name: string\n }\n V1OrganizationMemberResponse: {\n user_id: string\n user_name: string\n email?: string\n role_name: string\n mfa_enabled: boolean\n }\n V1OrganizationSlugResponse: {\n id: string\n name: string\n /** @enum {string} */\n plan?: 'free' | 'pro' | 'team' | 'enterprise'\n opt_in_tags: (\n | 'AI_SQL_GENERATOR_OPT_IN'\n | 'AI_DATA_GENERATOR_OPT_IN'\n | 'AI_LOG_GENERATOR_OPT_IN'\n )[]\n allowed_release_channels: ('internal' | 'alpha' | 'beta' | 'ga' | 'withdrawn' | 'preview')[]\n }\n OrganizationProjectClaimResponse: {\n project: {\n ref: string\n name: string\n }\n preview: {\n valid: boolean\n warnings: {\n key: string\n message: string\n }[]\n errors: {\n key: string\n message: string\n }[]\n info: {\n key: string\n message: string\n }[]\n members_exceeding_free_project_limit: {\n name: string\n limit: number\n }[]\n target_organization_eligible: boolean | null\n target_organization_has_free_project_slots: boolean | null\n /** @enum {string} */\n source_subscription_plan: 'free' | 'pro' | 'team' | 'enterprise'\n /** @enum {string|null} */\n target_subscription_plan: 'free' | 'pro' | 'team' | 'enterprise' | null\n }\n expires_at: string\n created_at: string\n /** Format: uuid */\n created_by: string\n }\n }\n responses: never\n parameters: never\n requestBodies: never\n headers: never\n pathItems: never\n}\nexport type $defs = Record\nexport interface operations {\n 'v1-get-a-branch-config': {\n parameters: {\n query?: never\n header?: never\n path: {\n /** @description Branch ID */\n branch_id: string\n }\n cookie?: never\n }\n requestBody?: never\n responses: {\n 200: {\n headers: {\n [name: string]: unknown\n }\n content: {\n 'application/json': components['schemas']['BranchDetailResponse']\n }\n }\n /** @description Failed to retrieve database branch */\n 500: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n }\n }\n 'v1-delete-a-branch': {\n parameters: {\n query?: never\n header?: never\n path: {\n /** @description Branch ID */\n branch_id: string\n }\n cookie?: never\n }\n requestBody?: never\n responses: {\n 200: {\n headers: {\n [name: string]: unknown\n }\n content: {\n 'application/json': components['schemas']['BranchDeleteResponse']\n }\n }\n /** @description Failed to delete database branch */\n 500: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n }\n }\n 'v1-update-a-branch-config': {\n parameters: {\n query?: never\n header?: never\n path: {\n /** @description Branch ID */\n branch_id: string\n }\n cookie?: never\n }\n requestBody: {\n content: {\n 'application/json': components['schemas']['UpdateBranchBody']\n }\n }\n responses: {\n 200: {\n headers: {\n [name: string]: unknown\n }\n content: {\n 'application/json': components['schemas']['BranchResponse']\n }\n }\n /** @description Failed to update database branch */\n 500: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n }\n }\n 'v1-push-a-branch': {\n parameters: {\n query?: never\n header?: never\n path: {\n /** @description Branch ID */\n branch_id: string\n }\n cookie?: never\n }\n requestBody: {\n content: {\n 'application/json': components['schemas']['BranchActionBody']\n }\n }\n responses: {\n 201: {\n headers: {\n [name: string]: unknown\n }\n content: {\n 'application/json': components['schemas']['BranchUpdateResponse']\n }\n }\n /** @description Failed to push database branch */\n 500: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n }\n }\n 'v1-merge-a-branch': {\n parameters: {\n query?: never\n header?: never\n path: {\n /** @description Branch ID */\n branch_id: string\n }\n cookie?: never\n }\n requestBody: {\n content: {\n 'application/json': components['schemas']['BranchActionBody']\n }\n }\n responses: {\n 201: {\n headers: {\n [name: string]: unknown\n }\n content: {\n 'application/json': components['schemas']['BranchUpdateResponse']\n }\n }\n /** @description Failed to merge database branch */\n 500: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n }\n }\n 'v1-reset-a-branch': {\n parameters: {\n query?: never\n header?: never\n path: {\n /** @description Branch ID */\n branch_id: string\n }\n cookie?: never\n }\n requestBody: {\n content: {\n 'application/json': components['schemas']['BranchActionBody']\n }\n }\n responses: {\n 201: {\n headers: {\n [name: string]: unknown\n }\n content: {\n 'application/json': components['schemas']['BranchUpdateResponse']\n }\n }\n /** @description Failed to reset database branch */\n 500: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n }\n }\n 'v1-diff-a-branch': {\n parameters: {\n query?: {\n included_schemas?: string\n }\n header?: never\n path: {\n /** @description Branch ID */\n branch_id: string\n }\n cookie?: never\n }\n requestBody?: never\n responses: {\n 200: {\n headers: {\n [name: string]: unknown\n }\n content: {\n 'text/plain': string\n }\n }\n /** @description Failed to diff database branch */\n 500: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n }\n }\n 'v1-list-all-projects': {\n parameters: {\n query?: never\n header?: never\n path?: never\n cookie?: never\n }\n requestBody?: never\n responses: {\n 200: {\n headers: {\n [name: string]: unknown\n }\n content: {\n 'application/json': components['schemas']['V1ProjectWithDatabaseResponse'][]\n }\n }\n }\n }\n 'v1-create-a-project': {\n parameters: {\n query?: never\n header?: never\n path?: never\n cookie?: never\n }\n requestBody: {\n content: {\n 'application/json': components['schemas']['V1CreateProjectBody']\n }\n }\n responses: {\n 201: {\n headers: {\n [name: string]: unknown\n }\n content: {\n 'application/json': components['schemas']['V1ProjectResponse']\n }\n }\n }\n }\n 'v1-list-all-organizations': {\n parameters: {\n query?: never\n header?: never\n path?: never\n cookie?: never\n }\n requestBody?: never\n responses: {\n 200: {\n headers: {\n [name: string]: unknown\n }\n content: {\n 'application/json': components['schemas']['OrganizationResponseV1'][]\n }\n }\n /** @description Unexpected error listing organizations */\n 500: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n }\n }\n 'v1-create-an-organization': {\n parameters: {\n query?: never\n header?: never\n path?: never\n cookie?: never\n }\n requestBody: {\n content: {\n 'application/json': components['schemas']['CreateOrganizationV1']\n }\n }\n responses: {\n 201: {\n headers: {\n [name: string]: unknown\n }\n content: {\n 'application/json': components['schemas']['OrganizationResponseV1']\n }\n }\n /** @description Unexpected error creating an organization */\n 500: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n }\n }\n 'v1-authorize-user': {\n parameters: {\n query: {\n client_id: string\n response_type: 'code' | 'token' | 'id_token token'\n redirect_uri: string\n scope?: string\n state?: string\n response_mode?: string\n code_challenge?: string\n code_challenge_method?: 'plain' | 'sha256' | 'S256'\n }\n header?: never\n path?: never\n cookie?: never\n }\n requestBody?: never\n responses: {\n 204: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n }\n }\n 'v1-exchange-oauth-token': {\n parameters: {\n query?: never\n header?: never\n path?: never\n cookie?: never\n }\n requestBody: {\n content: {\n 'application/x-www-form-urlencoded': components['schemas']['OAuthTokenBody']\n }\n }\n responses: {\n 201: {\n headers: {\n [name: string]: unknown\n }\n content: {\n 'application/json': components['schemas']['OAuthTokenResponse']\n }\n }\n }\n }\n 'v1-revoke-token': {\n parameters: {\n query?: never\n header?: never\n path?: never\n cookie?: never\n }\n requestBody: {\n content: {\n 'application/json': components['schemas']['OAuthRevokeTokenBody']\n }\n }\n responses: {\n 204: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n }\n }\n 'v1-oauth-authorize-project-claim': {\n parameters: {\n query: {\n /** @description Project ref */\n project_ref: string\n client_id: string\n response_type: 'code' | 'token' | 'id_token token'\n redirect_uri: string\n state?: string\n response_mode?: string\n code_challenge?: string\n code_challenge_method?: 'plain' | 'sha256' | 'S256'\n }\n header?: never\n path?: never\n cookie?: never\n }\n requestBody?: never\n responses: {\n 204: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n }\n }\n 'v1-list-all-snippets': {\n parameters: {\n query?: {\n /** @description Project ref */\n project_ref?: string\n cursor?: string\n limit?: string\n sort_by?: 'name' | 'inserted_at'\n sort_order?: 'asc' | 'desc'\n }\n header?: never\n path?: never\n cookie?: never\n }\n requestBody?: never\n responses: {\n 200: {\n headers: {\n [name: string]: unknown\n }\n content: {\n 'application/json': components['schemas']['SnippetList']\n }\n }\n /** @description Failed to list user's SQL snippets */\n 500: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n }\n }\n 'v1-get-a-snippet': {\n parameters: {\n query?: never\n header?: never\n path: {\n id: string\n }\n cookie?: never\n }\n requestBody?: never\n responses: {\n 200: {\n headers: {\n [name: string]: unknown\n }\n content: {\n 'application/json': components['schemas']['SnippetResponse']\n }\n }\n /** @description Failed to retrieve SQL snippet */\n 500: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n }\n }\n 'v1-get-project-api-keys': {\n parameters: {\n query?: {\n /** @description Boolean string, true or false */\n reveal?: boolean\n }\n header?: never\n path: {\n /** @description Project ref */\n ref: string\n }\n cookie?: never\n }\n requestBody?: never\n responses: {\n 200: {\n headers: {\n [name: string]: unknown\n }\n content: {\n 'application/json': components['schemas']['ApiKeyResponse'][]\n }\n }\n 403: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n }\n }\n 'v1-create-project-api-key': {\n parameters: {\n query?: {\n /** @description Boolean string, true or false */\n reveal?: boolean\n }\n header?: never\n path: {\n /** @description Project ref */\n ref: string\n }\n cookie?: never\n }\n requestBody: {\n content: {\n 'application/json': components['schemas']['CreateApiKeyBody']\n }\n }\n responses: {\n 201: {\n headers: {\n [name: string]: unknown\n }\n content: {\n 'application/json': components['schemas']['ApiKeyResponse']\n }\n }\n 403: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n }\n }\n 'v1-get-project-legacy-api-keys': {\n parameters: {\n query?: never\n header?: never\n path: {\n /** @description Project ref */\n ref: string\n }\n cookie?: never\n }\n requestBody?: never\n responses: {\n 200: {\n headers: {\n [name: string]: unknown\n }\n content: {\n 'application/json': components['schemas']['LegacyApiKeysResponse']\n }\n }\n 403: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n }\n }\n 'v1-update-project-legacy-api-keys': {\n parameters: {\n query: {\n /** @description Boolean string, true or false */\n enabled: boolean\n }\n header?: never\n path: {\n /** @description Project ref */\n ref: string\n }\n cookie?: never\n }\n requestBody?: never\n responses: {\n 200: {\n headers: {\n [name: string]: unknown\n }\n content: {\n 'application/json': components['schemas']['LegacyApiKeysResponse']\n }\n }\n 403: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n }\n }\n 'v1-get-project-api-key': {\n parameters: {\n query?: {\n /** @description Boolean string, true or false */\n reveal?: boolean\n }\n header?: never\n path: {\n /** @description Project ref */\n ref: string\n id: string\n }\n cookie?: never\n }\n requestBody?: never\n responses: {\n 200: {\n headers: {\n [name: string]: unknown\n }\n content: {\n 'application/json': components['schemas']['ApiKeyResponse']\n }\n }\n 403: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n }\n }\n 'v1-delete-project-api-key': {\n parameters: {\n query?: {\n /** @description Boolean string, true or false */\n reveal?: boolean\n /** @description Boolean string, true or false */\n was_compromised?: boolean\n reason?: string\n }\n header?: never\n path: {\n /** @description Project ref */\n ref: string\n id: string\n }\n cookie?: never\n }\n requestBody?: never\n responses: {\n 200: {\n headers: {\n [name: string]: unknown\n }\n content: {\n 'application/json': components['schemas']['ApiKeyResponse']\n }\n }\n 403: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n }\n }\n 'v1-update-project-api-key': {\n parameters: {\n query?: {\n /** @description Boolean string, true or false */\n reveal?: boolean\n }\n header?: never\n path: {\n /** @description Project ref */\n ref: string\n id: string\n }\n cookie?: never\n }\n requestBody: {\n content: {\n 'application/json': components['schemas']['UpdateApiKeyBody']\n }\n }\n responses: {\n 200: {\n headers: {\n [name: string]: unknown\n }\n content: {\n 'application/json': components['schemas']['ApiKeyResponse']\n }\n }\n 403: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n }\n }\n 'v1-list-all-branches': {\n parameters: {\n query?: never\n header?: never\n path: {\n /** @description Project ref */\n ref: string\n }\n cookie?: never\n }\n requestBody?: never\n responses: {\n 200: {\n headers: {\n [name: string]: unknown\n }\n content: {\n 'application/json': components['schemas']['BranchResponse'][]\n }\n }\n 403: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n /** @description Failed to retrieve database branches */\n 500: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n }\n }\n 'v1-create-a-branch': {\n parameters: {\n query?: never\n header?: never\n path: {\n /** @description Project ref */\n ref: string\n }\n cookie?: never\n }\n requestBody: {\n content: {\n 'application/json': components['schemas']['CreateBranchBody']\n }\n }\n responses: {\n 201: {\n headers: {\n [name: string]: unknown\n }\n content: {\n 'application/json': components['schemas']['BranchResponse']\n }\n }\n 403: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n /** @description Failed to create database branch */\n 500: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n }\n }\n 'v1-disable-preview-branching': {\n parameters: {\n query?: never\n header?: never\n path: {\n /** @description Project ref */\n ref: string\n }\n cookie?: never\n }\n requestBody?: never\n responses: {\n 200: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n 403: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n /** @description Failed to disable preview branching */\n 500: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n }\n }\n 'v1-get-hostname-config': {\n parameters: {\n query?: never\n header?: never\n path: {\n /** @description Project ref */\n ref: string\n }\n cookie?: never\n }\n requestBody?: never\n responses: {\n 200: {\n headers: {\n [name: string]: unknown\n }\n content: {\n 'application/json': components['schemas']['UpdateCustomHostnameResponse']\n }\n }\n 403: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n /** @description Failed to retrieve project's custom hostname config */\n 500: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n }\n }\n 'v1-Delete hostname config': {\n parameters: {\n query?: never\n header?: never\n path: {\n /** @description Project ref */\n ref: string\n }\n cookie?: never\n }\n requestBody?: never\n responses: {\n 200: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n 403: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n /** @description Failed to delete project custom hostname configuration */\n 500: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n }\n }\n 'v1-update-hostname-config': {\n parameters: {\n query?: never\n header?: never\n path: {\n /** @description Project ref */\n ref: string\n }\n cookie?: never\n }\n requestBody: {\n content: {\n 'application/json': components['schemas']['UpdateCustomHostnameBody']\n }\n }\n responses: {\n 201: {\n headers: {\n [name: string]: unknown\n }\n content: {\n 'application/json': components['schemas']['UpdateCustomHostnameResponse']\n }\n }\n 403: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n /** @description Failed to update project custom hostname configuration */\n 500: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n }\n }\n 'v1-verify-dns-config': {\n parameters: {\n query?: never\n header?: never\n path: {\n /** @description Project ref */\n ref: string\n }\n cookie?: never\n }\n requestBody?: never\n responses: {\n 201: {\n headers: {\n [name: string]: unknown\n }\n content: {\n 'application/json': components['schemas']['UpdateCustomHostnameResponse']\n }\n }\n 403: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n /** @description Failed to verify project custom hostname configuration */\n 500: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n }\n }\n 'v1-activate-custom-hostname': {\n parameters: {\n query?: never\n header?: never\n path: {\n /** @description Project ref */\n ref: string\n }\n cookie?: never\n }\n requestBody?: never\n responses: {\n 201: {\n headers: {\n [name: string]: unknown\n }\n content: {\n 'application/json': components['schemas']['UpdateCustomHostnameResponse']\n }\n }\n 403: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n /** @description Failed to activate project custom hostname configuration */\n 500: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n }\n }\n 'v1-list-all-network-bans': {\n parameters: {\n query?: never\n header?: never\n path: {\n /** @description Project ref */\n ref: string\n }\n cookie?: never\n }\n requestBody?: never\n responses: {\n 201: {\n headers: {\n [name: string]: unknown\n }\n content: {\n 'application/json': components['schemas']['NetworkBanResponse']\n }\n }\n 403: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n /** @description Failed to retrieve project's network bans */\n 500: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n }\n }\n 'v1-list-all-network-bans-enriched': {\n parameters: {\n query?: never\n header?: never\n path: {\n /** @description Project ref */\n ref: string\n }\n cookie?: never\n }\n requestBody?: never\n responses: {\n 201: {\n headers: {\n [name: string]: unknown\n }\n content: {\n 'application/json': components['schemas']['NetworkBanResponseEnriched']\n }\n }\n 403: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n /** @description Failed to retrieve project's enriched network bans */\n 500: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n }\n }\n 'v1-delete-network-bans': {\n parameters: {\n query?: never\n header?: never\n path: {\n /** @description Project ref */\n ref: string\n }\n cookie?: never\n }\n requestBody: {\n content: {\n 'application/json': components['schemas']['RemoveNetworkBanRequest']\n }\n }\n responses: {\n 200: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n 403: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n /** @description Failed to remove network bans. */\n 500: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n }\n }\n 'v1-get-network-restrictions': {\n parameters: {\n query?: never\n header?: never\n path: {\n /** @description Project ref */\n ref: string\n }\n cookie?: never\n }\n requestBody?: never\n responses: {\n 200: {\n headers: {\n [name: string]: unknown\n }\n content: {\n 'application/json': components['schemas']['NetworkRestrictionsResponse']\n }\n }\n 403: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n /** @description Failed to retrieve project's network restrictions */\n 500: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n }\n }\n 'v1-update-network-restrictions': {\n parameters: {\n query?: never\n header?: never\n path: {\n /** @description Project ref */\n ref: string\n }\n cookie?: never\n }\n requestBody: {\n content: {\n 'application/json': components['schemas']['NetworkRestrictionsRequest']\n }\n }\n responses: {\n 201: {\n headers: {\n [name: string]: unknown\n }\n content: {\n 'application/json': components['schemas']['NetworkRestrictionsResponse']\n }\n }\n 403: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n /** @description Failed to update project network restrictions */\n 500: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n }\n }\n 'v1-get-pgsodium-config': {\n parameters: {\n query?: never\n header?: never\n path: {\n /** @description Project ref */\n ref: string\n }\n cookie?: never\n }\n requestBody?: never\n responses: {\n 200: {\n headers: {\n [name: string]: unknown\n }\n content: {\n 'application/json': components['schemas']['PgsodiumConfigResponse']\n }\n }\n 403: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n /** @description Failed to retrieve project's pgsodium config */\n 500: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n }\n }\n 'v1-update-pgsodium-config': {\n parameters: {\n query?: never\n header?: never\n path: {\n /** @description Project ref */\n ref: string\n }\n cookie?: never\n }\n requestBody: {\n content: {\n 'application/json': components['schemas']['UpdatePgsodiumConfigBody']\n }\n }\n responses: {\n 200: {\n headers: {\n [name: string]: unknown\n }\n content: {\n 'application/json': components['schemas']['PgsodiumConfigResponse']\n }\n }\n 403: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n /** @description Failed to update project's pgsodium config */\n 500: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n }\n }\n 'v1-get-postgrest-service-config': {\n parameters: {\n query?: never\n header?: never\n path: {\n /** @description Project ref */\n ref: string\n }\n cookie?: never\n }\n requestBody?: never\n responses: {\n 200: {\n headers: {\n [name: string]: unknown\n }\n content: {\n 'application/json': components['schemas']['PostgrestConfigWithJWTSecretResponse']\n }\n }\n 403: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n /** @description Failed to retrieve project's postgrest config */\n 500: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n }\n }\n 'v1-update-postgrest-service-config': {\n parameters: {\n query?: never\n header?: never\n path: {\n /** @description Project ref */\n ref: string\n }\n cookie?: never\n }\n requestBody: {\n content: {\n 'application/json': components['schemas']['V1UpdatePostgrestConfigBody']\n }\n }\n responses: {\n 200: {\n headers: {\n [name: string]: unknown\n }\n content: {\n 'application/json': components['schemas']['V1PostgrestConfigResponse']\n }\n }\n 403: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n /** @description Failed to update project's postgrest config */\n 500: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n }\n }\n 'v1-get-project': {\n parameters: {\n query?: never\n header?: never\n path: {\n /** @description Project ref */\n ref: string\n }\n cookie?: never\n }\n requestBody?: never\n responses: {\n 200: {\n headers: {\n [name: string]: unknown\n }\n content: {\n 'application/json': components['schemas']['V1ProjectWithDatabaseResponse']\n }\n }\n 403: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n /** @description Failed to retrieve project */\n 500: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n }\n }\n 'v1-delete-a-project': {\n parameters: {\n query?: never\n header?: never\n path: {\n /** @description Project ref */\n ref: string\n }\n cookie?: never\n }\n requestBody?: never\n responses: {\n 200: {\n headers: {\n [name: string]: unknown\n }\n content: {\n 'application/json': components['schemas']['V1ProjectRefResponse']\n }\n }\n 403: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n }\n }\n 'v1-list-all-secrets': {\n parameters: {\n query?: never\n header?: never\n path: {\n /** @description Project ref */\n ref: string\n }\n cookie?: never\n }\n requestBody?: never\n responses: {\n 200: {\n headers: {\n [name: string]: unknown\n }\n content: {\n 'application/json': components['schemas']['SecretResponse'][]\n }\n }\n 403: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n /** @description Failed to retrieve project's secrets */\n 500: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n }\n }\n 'v1-bulk-create-secrets': {\n parameters: {\n query?: never\n header?: never\n path: {\n /** @description Project ref */\n ref: string\n }\n cookie?: never\n }\n requestBody: {\n content: {\n 'application/json': components['schemas']['CreateSecretBody']\n }\n }\n responses: {\n 201: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n 403: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n /** @description Failed to create project's secrets */\n 500: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n }\n }\n 'v1-bulk-delete-secrets': {\n parameters: {\n query?: never\n header?: never\n path: {\n /** @description Project ref */\n ref: string\n }\n cookie?: never\n }\n requestBody: {\n content: {\n 'application/json': string[]\n }\n }\n responses: {\n 200: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n 403: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n /** @description Failed to delete secrets with given names */\n 500: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n }\n }\n 'v1-get-ssl-enforcement-config': {\n parameters: {\n query?: never\n header?: never\n path: {\n /** @description Project ref */\n ref: string\n }\n cookie?: never\n }\n requestBody?: never\n responses: {\n 200: {\n headers: {\n [name: string]: unknown\n }\n content: {\n 'application/json': components['schemas']['SslEnforcementResponse']\n }\n }\n 403: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n /** @description Failed to retrieve project's SSL enforcement config */\n 500: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n }\n }\n 'v1-update-ssl-enforcement-config': {\n parameters: {\n query?: never\n header?: never\n path: {\n /** @description Project ref */\n ref: string\n }\n cookie?: never\n }\n requestBody: {\n content: {\n 'application/json': components['schemas']['SslEnforcementRequest']\n }\n }\n responses: {\n 200: {\n headers: {\n [name: string]: unknown\n }\n content: {\n 'application/json': components['schemas']['SslEnforcementResponse']\n }\n }\n 403: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n /** @description Failed to update project's SSL enforcement configuration. */\n 500: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n }\n }\n 'v1-generate-typescript-types': {\n parameters: {\n query?: {\n included_schemas?: string\n }\n header?: never\n path: {\n /** @description Project ref */\n ref: string\n }\n cookie?: never\n }\n requestBody?: never\n responses: {\n 200: {\n headers: {\n [name: string]: unknown\n }\n content: {\n 'application/json': components['schemas']['TypescriptResponse']\n }\n }\n 403: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n /** @description Failed to generate TypeScript types */\n 500: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n }\n }\n 'v1-get-vanity-subdomain-config': {\n parameters: {\n query?: never\n header?: never\n path: {\n /** @description Project ref */\n ref: string\n }\n cookie?: never\n }\n requestBody?: never\n responses: {\n 200: {\n headers: {\n [name: string]: unknown\n }\n content: {\n 'application/json': components['schemas']['VanitySubdomainConfigResponse']\n }\n }\n 403: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n /** @description Failed to get project vanity subdomain configuration */\n 500: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n }\n }\n 'v1-deactivate-vanity-subdomain-config': {\n parameters: {\n query?: never\n header?: never\n path: {\n /** @description Project ref */\n ref: string\n }\n cookie?: never\n }\n requestBody?: never\n responses: {\n 200: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n 403: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n /** @description Failed to delete project vanity subdomain configuration */\n 500: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n }\n }\n 'v1-check-vanity-subdomain-availability': {\n parameters: {\n query?: never\n header?: never\n path: {\n /** @description Project ref */\n ref: string\n }\n cookie?: never\n }\n requestBody: {\n content: {\n 'application/json': components['schemas']['VanitySubdomainBody']\n }\n }\n responses: {\n 201: {\n headers: {\n [name: string]: unknown\n }\n content: {\n 'application/json': components['schemas']['SubdomainAvailabilityResponse']\n }\n }\n 403: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n /** @description Failed to check project vanity subdomain configuration */\n 500: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n }\n }\n 'v1-activate-vanity-subdomain-config': {\n parameters: {\n query?: never\n header?: never\n path: {\n /** @description Project ref */\n ref: string\n }\n cookie?: never\n }\n requestBody: {\n content: {\n 'application/json': components['schemas']['VanitySubdomainBody']\n }\n }\n responses: {\n 201: {\n headers: {\n [name: string]: unknown\n }\n content: {\n 'application/json': components['schemas']['ActivateVanitySubdomainResponse']\n }\n }\n 403: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n /** @description Failed to activate project vanity subdomain configuration */\n 500: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n }\n }\n 'v1-upgrade-postgres-version': {\n parameters: {\n query?: never\n header?: never\n path: {\n /** @description Project ref */\n ref: string\n }\n cookie?: never\n }\n requestBody: {\n content: {\n 'application/json': components['schemas']['UpgradeDatabaseBody']\n }\n }\n responses: {\n 201: {\n headers: {\n [name: string]: unknown\n }\n content: {\n 'application/json': components['schemas']['ProjectUpgradeInitiateResponse']\n }\n }\n 403: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n /** @description Failed to initiate project upgrade */\n 500: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n }\n }\n 'v1-get-postgres-upgrade-eligibility': {\n parameters: {\n query?: never\n header?: never\n path: {\n /** @description Project ref */\n ref: string\n }\n cookie?: never\n }\n requestBody?: never\n responses: {\n 200: {\n headers: {\n [name: string]: unknown\n }\n content: {\n 'application/json': components['schemas']['ProjectUpgradeEligibilityResponse']\n }\n }\n 403: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n /** @description Failed to determine project upgrade eligibility */\n 500: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n }\n }\n 'v1-get-postgres-upgrade-status': {\n parameters: {\n query?: {\n tracking_id?: string\n }\n header?: never\n path: {\n /** @description Project ref */\n ref: string\n }\n cookie?: never\n }\n requestBody?: never\n responses: {\n 200: {\n headers: {\n [name: string]: unknown\n }\n content: {\n 'application/json': components['schemas']['DatabaseUpgradeStatusResponse']\n }\n }\n 403: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n /** @description Failed to retrieve project upgrade status */\n 500: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n }\n }\n 'v1-get-readonly-mode-status': {\n parameters: {\n query?: never\n header?: never\n path: {\n /** @description Project ref */\n ref: string\n }\n cookie?: never\n }\n requestBody?: never\n responses: {\n 200: {\n headers: {\n [name: string]: unknown\n }\n content: {\n 'application/json': components['schemas']['ReadOnlyStatusResponse']\n }\n }\n 403: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n /** @description Failed to get project readonly mode status */\n 500: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n }\n }\n 'v1-disable-readonly-mode-temporarily': {\n parameters: {\n query?: never\n header?: never\n path: {\n /** @description Project ref */\n ref: string\n }\n cookie?: never\n }\n requestBody?: never\n responses: {\n 201: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n 403: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n /** @description Failed to disable project's readonly mode */\n 500: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n }\n }\n 'v1-setup-a-read-replica': {\n parameters: {\n query?: never\n header?: never\n path: {\n /** @description Project ref */\n ref: string\n }\n cookie?: never\n }\n requestBody: {\n content: {\n 'application/json': components['schemas']['SetUpReadReplicaBody']\n }\n }\n responses: {\n 201: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n 403: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n /** @description Failed to set up read replica */\n 500: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n }\n }\n 'v1-remove-a-read-replica': {\n parameters: {\n query?: never\n header?: never\n path: {\n /** @description Project ref */\n ref: string\n }\n cookie?: never\n }\n requestBody: {\n content: {\n 'application/json': components['schemas']['RemoveReadReplicaBody']\n }\n }\n responses: {\n 201: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n 403: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n /** @description Failed to remove read replica */\n 500: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n }\n }\n 'v1-get-services-health': {\n parameters: {\n query: {\n services: ('auth' | 'db' | 'pooler' | 'realtime' | 'rest' | 'storage')[]\n timeout_ms?: number\n }\n header?: never\n path: {\n /** @description Project ref */\n ref: string\n }\n cookie?: never\n }\n requestBody?: never\n responses: {\n 200: {\n headers: {\n [name: string]: unknown\n }\n content: {\n 'application/json': components['schemas']['V1ServiceHealthResponse'][]\n }\n }\n 403: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n /** @description Failed to retrieve project's service health status */\n 500: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n }\n }\n 'v1-get-project-signing-keys': {\n parameters: {\n query?: never\n header?: never\n path: {\n /** @description Project ref */\n ref: string\n }\n cookie?: never\n }\n requestBody?: never\n responses: {\n 200: {\n headers: {\n [name: string]: unknown\n }\n content: {\n 'application/json': components['schemas']['SigningKeysResponse']\n }\n }\n 403: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n }\n }\n 'v1-create-project-signing-key': {\n parameters: {\n query?: never\n header?: never\n path: {\n /** @description Project ref */\n ref: string\n }\n cookie?: never\n }\n requestBody: {\n content: {\n 'application/json': components['schemas']['CreateSigningKeyBody']\n }\n }\n responses: {\n 201: {\n headers: {\n [name: string]: unknown\n }\n content: {\n 'application/json': components['schemas']['SigningKeyResponse']\n }\n }\n 403: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n }\n }\n 'v1-get-project-signing-key': {\n parameters: {\n query?: never\n header?: never\n path: {\n id: string\n /** @description Project ref */\n ref: string\n }\n cookie?: never\n }\n requestBody?: never\n responses: {\n 200: {\n headers: {\n [name: string]: unknown\n }\n content: {\n 'application/json': components['schemas']['SigningKeyResponse']\n }\n }\n 403: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n }\n }\n 'v1-remove-project-signing-key': {\n parameters: {\n query?: never\n header?: never\n path: {\n id: string\n /** @description Project ref */\n ref: string\n }\n cookie?: never\n }\n requestBody?: never\n responses: {\n 200: {\n headers: {\n [name: string]: unknown\n }\n content: {\n 'application/json': components['schemas']['SigningKeyResponse']\n }\n }\n 403: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n }\n }\n 'v1-update-project-signing-key': {\n parameters: {\n query?: never\n header?: never\n path: {\n id: string\n /** @description Project ref */\n ref: string\n }\n cookie?: never\n }\n requestBody: {\n content: {\n 'application/json': components['schemas']['UpdateSigningKeyBody']\n }\n }\n responses: {\n 200: {\n headers: {\n [name: string]: unknown\n }\n content: {\n 'application/json': components['schemas']['SigningKeyResponse']\n }\n }\n 403: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n }\n }\n 'v1-get-storage-config': {\n parameters: {\n query?: never\n header?: never\n path: {\n /** @description Project ref */\n ref: string\n }\n cookie?: never\n }\n requestBody?: never\n responses: {\n 200: {\n headers: {\n [name: string]: unknown\n }\n content: {\n 'application/json': components['schemas']['StorageConfigResponse']\n }\n }\n 403: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n /** @description Failed to retrieve project's storage config */\n 500: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n }\n }\n 'v1-update-storage-config': {\n parameters: {\n query?: never\n header?: never\n path: {\n /** @description Project ref */\n ref: string\n }\n cookie?: never\n }\n requestBody: {\n content: {\n 'application/json': components['schemas']['UpdateStorageConfigBody']\n }\n }\n responses: {\n 200: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n 403: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n /** @description Failed to update project's storage config */\n 500: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n }\n }\n 'v1-get-postgres-config': {\n parameters: {\n query?: never\n header?: never\n path: {\n /** @description Project ref */\n ref: string\n }\n cookie?: never\n }\n requestBody?: never\n responses: {\n 200: {\n headers: {\n [name: string]: unknown\n }\n content: {\n 'application/json': components['schemas']['PostgresConfigResponse']\n }\n }\n 403: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n /** @description Failed to retrieve project's Postgres config */\n 500: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n }\n }\n 'v1-update-postgres-config': {\n parameters: {\n query?: never\n header?: never\n path: {\n /** @description Project ref */\n ref: string\n }\n cookie?: never\n }\n requestBody: {\n content: {\n 'application/json': components['schemas']['UpdatePostgresConfigBody']\n }\n }\n responses: {\n 200: {\n headers: {\n [name: string]: unknown\n }\n content: {\n 'application/json': components['schemas']['PostgresConfigResponse']\n }\n }\n 403: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n /** @description Failed to update project's Postgres config */\n 500: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n }\n }\n 'v1-get-project-pgbouncer-config': {\n parameters: {\n query?: never\n header?: never\n path: {\n /** @description Project ref */\n ref: string\n }\n cookie?: never\n }\n requestBody?: never\n responses: {\n 200: {\n headers: {\n [name: string]: unknown\n }\n content: {\n 'application/json': components['schemas']['V1PgbouncerConfigResponse']\n }\n }\n 403: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n /** @description Failed to retrieve project's pgbouncer config */\n 500: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n }\n }\n 'v1-get-pooler-config': {\n parameters: {\n query?: never\n header?: never\n path: {\n /** @description Project ref */\n ref: string\n }\n cookie?: never\n }\n requestBody?: never\n responses: {\n 200: {\n headers: {\n [name: string]: unknown\n }\n content: {\n 'application/json': components['schemas']['SupavisorConfigResponse'][]\n }\n }\n /** @description Failed to retrieve project's supavisor config */\n 500: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n }\n }\n 'v1-update-pooler-config': {\n parameters: {\n query?: never\n header?: never\n path: {\n /** @description Project ref */\n ref: string\n }\n cookie?: never\n }\n requestBody: {\n content: {\n 'application/json': components['schemas']['UpdateSupavisorConfigBody']\n }\n }\n responses: {\n 200: {\n headers: {\n [name: string]: unknown\n }\n content: {\n 'application/json': components['schemas']['UpdateSupavisorConfigResponse']\n }\n }\n 403: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n /** @description Failed to update project's supavisor config */\n 500: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n }\n }\n 'v1-get-auth-service-config': {\n parameters: {\n query?: never\n header?: never\n path: {\n /** @description Project ref */\n ref: string\n }\n cookie?: never\n }\n requestBody?: never\n responses: {\n 200: {\n headers: {\n [name: string]: unknown\n }\n content: {\n 'application/json': components['schemas']['AuthConfigResponse']\n }\n }\n 403: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n /** @description Failed to retrieve project's auth config */\n 500: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n }\n }\n 'v1-update-auth-service-config': {\n parameters: {\n query?: never\n header?: never\n path: {\n /** @description Project ref */\n ref: string\n }\n cookie?: never\n }\n requestBody: {\n content: {\n 'application/json': components['schemas']['UpdateAuthConfigBody']\n }\n }\n responses: {\n 200: {\n headers: {\n [name: string]: unknown\n }\n content: {\n 'application/json': components['schemas']['AuthConfigResponse']\n }\n }\n 403: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n /** @description Failed to update project's auth config */\n 500: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n }\n }\n 'v1-list-project-tpa-integrations': {\n parameters: {\n query?: never\n header?: never\n path: {\n /** @description Project ref */\n ref: string\n }\n cookie?: never\n }\n requestBody?: never\n responses: {\n 200: {\n headers: {\n [name: string]: unknown\n }\n content: {\n 'application/json': components['schemas']['ThirdPartyAuth'][]\n }\n }\n 403: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n }\n }\n 'v1-create-project-tpa-integration': {\n parameters: {\n query?: never\n header?: never\n path: {\n /** @description Project ref */\n ref: string\n }\n cookie?: never\n }\n requestBody: {\n content: {\n 'application/json': components['schemas']['CreateThirdPartyAuthBody']\n }\n }\n responses: {\n 201: {\n headers: {\n [name: string]: unknown\n }\n content: {\n 'application/json': components['schemas']['ThirdPartyAuth']\n }\n }\n 403: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n }\n }\n 'v1-get-project-tpa-integration': {\n parameters: {\n query?: never\n header?: never\n path: {\n /** @description Project ref */\n ref: string\n tpa_id: string\n }\n cookie?: never\n }\n requestBody?: never\n responses: {\n 200: {\n headers: {\n [name: string]: unknown\n }\n content: {\n 'application/json': components['schemas']['ThirdPartyAuth']\n }\n }\n 403: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n }\n }\n 'v1-delete-project-tpa-integration': {\n parameters: {\n query?: never\n header?: never\n path: {\n /** @description Project ref */\n ref: string\n tpa_id: string\n }\n cookie?: never\n }\n requestBody?: never\n responses: {\n 200: {\n headers: {\n [name: string]: unknown\n }\n content: {\n 'application/json': components['schemas']['ThirdPartyAuth']\n }\n }\n 403: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n }\n }\n 'v1-pause-a-project': {\n parameters: {\n query?: never\n header?: never\n path: {\n /** @description Project ref */\n ref: string\n }\n cookie?: never\n }\n requestBody?: never\n responses: {\n 200: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n 403: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n }\n }\n 'v1-list-available-restore-versions': {\n parameters: {\n query?: never\n header?: never\n path: {\n /** @description Project ref */\n ref: string\n }\n cookie?: never\n }\n requestBody?: never\n responses: {\n 200: {\n headers: {\n [name: string]: unknown\n }\n content: {\n 'application/json': components['schemas']['GetProjectAvailableRestoreVersionsResponse']\n }\n }\n 403: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n }\n }\n 'v1-restore-a-project': {\n parameters: {\n query?: never\n header?: never\n path: {\n /** @description Project ref */\n ref: string\n }\n cookie?: never\n }\n requestBody?: never\n responses: {\n 200: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n 403: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n }\n }\n 'v1-cancel-a-project-restoration': {\n parameters: {\n query?: never\n header?: never\n path: {\n /** @description Project ref */\n ref: string\n }\n cookie?: never\n }\n requestBody?: never\n responses: {\n 200: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n 403: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n }\n }\n 'v1-list-project-addons': {\n parameters: {\n query?: never\n header?: never\n path: {\n /** @description Project ref */\n ref: string\n }\n cookie?: never\n }\n requestBody?: never\n responses: {\n 200: {\n headers: {\n [name: string]: unknown\n }\n content: {\n 'application/json': components['schemas']['ListProjectAddonsResponse']\n }\n }\n 403: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n /** @description Failed to list project addons */\n 500: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n }\n }\n 'v1-apply-project-addon': {\n parameters: {\n query?: never\n header?: never\n path: {\n /** @description Project ref */\n ref: string\n }\n cookie?: never\n }\n requestBody: {\n content: {\n 'application/json': components['schemas']['ApplyProjectAddonBody']\n }\n }\n responses: {\n 200: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n 403: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n /** @description Failed to apply project addon */\n 500: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n }\n }\n 'v1-remove-project-addon': {\n parameters: {\n query?: never\n header?: never\n path: {\n /** @description Project ref */\n ref: string\n addon_variant: unknown\n }\n cookie?: never\n }\n requestBody?: never\n responses: {\n 200: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n 403: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n /** @description Failed to remove project addon */\n 500: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n }\n }\n 'v1-get-project-claim-token': {\n parameters: {\n query?: never\n header?: never\n path: {\n /** @description Project ref */\n ref: string\n }\n cookie?: never\n }\n requestBody?: never\n responses: {\n 200: {\n headers: {\n [name: string]: unknown\n }\n content: {\n 'application/json': components['schemas']['ProjectClaimTokenResponse']\n }\n }\n 403: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n }\n }\n 'v1-create-project-claim-token': {\n parameters: {\n query?: never\n header?: never\n path: {\n /** @description Project ref */\n ref: string\n }\n cookie?: never\n }\n requestBody?: never\n responses: {\n 200: {\n headers: {\n [name: string]: unknown\n }\n content: {\n 'application/json': components['schemas']['CreateProjectClaimTokenResponse']\n }\n }\n 403: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n }\n }\n 'v1-delete-project-claim-token': {\n parameters: {\n query?: never\n header?: never\n path: {\n /** @description Project ref */\n ref: string\n }\n cookie?: never\n }\n requestBody?: never\n responses: {\n 204: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n 403: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n }\n }\n 'v1-get-performance-advisors': {\n parameters: {\n query?: never\n header?: never\n path: {\n /** @description Project ref */\n ref: string\n }\n cookie?: never\n }\n requestBody?: never\n responses: {\n 200: {\n headers: {\n [name: string]: unknown\n }\n content: {\n 'application/json': components['schemas']['V1ProjectAdvisorsResponse']\n }\n }\n 403: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n }\n }\n 'v1-get-security-advisors': {\n parameters: {\n query?: {\n lint_type?: 'sql'\n }\n header?: never\n path: {\n /** @description Project ref */\n ref: string\n }\n cookie?: never\n }\n requestBody?: never\n responses: {\n 200: {\n headers: {\n [name: string]: unknown\n }\n content: {\n 'application/json': components['schemas']['V1ProjectAdvisorsResponse']\n }\n }\n 403: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n }\n }\n 'v1-get-project-logs': {\n parameters: {\n query?: {\n sql?: string\n iso_timestamp_start?: string\n iso_timestamp_end?: string\n }\n header?: never\n path: {\n /** @description Project ref */\n ref: string\n }\n cookie?: never\n }\n requestBody?: never\n responses: {\n 200: {\n headers: {\n [name: string]: unknown\n }\n content: {\n 'application/json': components['schemas']['AnalyticsResponse']\n }\n }\n 403: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n }\n }\n 'v1-get-project-usage-api-count': {\n parameters: {\n query?: {\n interval?: '15min' | '30min' | '1hr' | '3hr' | '1day' | '3day' | '7day'\n }\n header?: never\n path: {\n /** @description Project ref */\n ref: string\n }\n cookie?: never\n }\n requestBody?: never\n responses: {\n 200: {\n headers: {\n [name: string]: unknown\n }\n content: {\n 'application/json': components['schemas']['AnalyticsResponse']\n }\n }\n 403: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n /** @description Failed to get project's usage api counts */\n 500: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n }\n }\n 'v1-get-project-usage-request-count': {\n parameters: {\n query?: never\n header?: never\n path: {\n /** @description Project ref */\n ref: string\n }\n cookie?: never\n }\n requestBody?: never\n responses: {\n 200: {\n headers: {\n [name: string]: unknown\n }\n content: {\n 'application/json': components['schemas']['AnalyticsResponse']\n }\n }\n /** @description Failed to get project's usage api requests count */\n 500: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n }\n }\n 'v1-list-migration-history': {\n parameters: {\n query?: never\n header?: never\n path: {\n /** @description Project ref */\n ref: string\n }\n cookie?: never\n }\n requestBody?: never\n responses: {\n 200: {\n headers: {\n [name: string]: unknown\n }\n content: {\n 'application/json': components['schemas']['V1ListMigrationsResponse']\n }\n }\n 403: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n /** @description Failed to list database migrations */\n 500: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n }\n }\n 'v1-upsert-a-migration': {\n parameters: {\n query?: never\n header?: {\n /** @description A unique key to ensure the same migration is tracked only once. */\n 'Idempotency-Key'?: string\n }\n path: {\n /** @description Project ref */\n ref: string\n }\n cookie?: never\n }\n requestBody: {\n content: {\n 'application/json': components['schemas']['V1UpsertMigrationBody']\n }\n }\n responses: {\n 200: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n 403: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n /** @description Failed to upsert database migration */\n 500: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n }\n }\n 'v1-apply-a-migration': {\n parameters: {\n query?: never\n header?: {\n /** @description A unique key to ensure the same migration is tracked only once. */\n 'Idempotency-Key'?: string\n }\n path: {\n /** @description Project ref */\n ref: string\n }\n cookie?: never\n }\n requestBody: {\n content: {\n 'application/json': components['schemas']['V1CreateMigrationBody']\n }\n }\n responses: {\n 200: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n 403: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n /** @description Failed to apply database migration */\n 500: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n }\n }\n 'v1-run-a-query': {\n parameters: {\n query?: never\n header?: never\n path: {\n /** @description Project ref */\n ref: string\n }\n cookie?: never\n }\n requestBody: {\n content: {\n 'application/json': components['schemas']['V1RunQueryBody']\n }\n }\n responses: {\n 201: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n 403: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n /** @description Failed to run sql query */\n 500: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n }\n }\n 'v1-enable-database-webhook': {\n parameters: {\n query?: never\n header?: never\n path: {\n /** @description Project ref */\n ref: string\n }\n cookie?: never\n }\n requestBody?: never\n responses: {\n 201: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n 403: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n /** @description Failed to enable Database Webhooks on the project */\n 500: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n }\n }\n 'v1-get-database-metadata': {\n parameters: {\n query?: never\n header?: never\n path: {\n /** @description Project ref */\n ref: string\n }\n cookie?: never\n }\n requestBody?: never\n responses: {\n 200: {\n headers: {\n [name: string]: unknown\n }\n content: {\n 'application/json': components['schemas']['GetProjectDbMetadataResponse']\n }\n }\n 403: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n }\n }\n 'v1-list-all-functions': {\n parameters: {\n query?: never\n header?: never\n path: {\n /** @description Project ref */\n ref: string\n }\n cookie?: never\n }\n requestBody?: never\n responses: {\n 200: {\n headers: {\n [name: string]: unknown\n }\n content: {\n 'application/json': components['schemas']['FunctionResponse'][]\n }\n }\n 403: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n /** @description Failed to retrieve project's functions */\n 500: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n }\n }\n 'v1-bulk-update-functions': {\n parameters: {\n query?: never\n header?: never\n path: {\n /** @description Project ref */\n ref: string\n }\n cookie?: never\n }\n requestBody: {\n content: {\n 'application/json': components['schemas']['BulkUpdateFunctionBody']\n }\n }\n responses: {\n 200: {\n headers: {\n [name: string]: unknown\n }\n content: {\n 'application/json': components['schemas']['BulkUpdateFunctionResponse']\n }\n }\n 403: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n /** @description Failed to update functions */\n 500: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n }\n }\n 'v1-create-a-function': {\n parameters: {\n query?: {\n slug?: string\n name?: string\n /** @description Boolean string, true or false */\n verify_jwt?: boolean\n /** @description Boolean string, true or false */\n import_map?: boolean\n entrypoint_path?: string\n import_map_path?: string\n }\n header?: never\n path: {\n /** @description Project ref */\n ref: string\n }\n cookie?: never\n }\n requestBody: {\n content: {\n 'application/vnd.denoland.eszip': string\n 'application/json': components['schemas']['V1CreateFunctionBody']\n }\n }\n responses: {\n 201: {\n headers: {\n [name: string]: unknown\n }\n content: {\n 'application/json': components['schemas']['FunctionResponse']\n }\n }\n 403: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n /** @description Failed to create project's function */\n 500: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n }\n }\n 'v1-deploy-a-function': {\n parameters: {\n query?: {\n slug?: string\n /** @description Boolean string, true or false */\n bundleOnly?: boolean\n }\n header?: never\n path: {\n /** @description Project ref */\n ref: string\n }\n cookie?: never\n }\n requestBody: {\n content: {\n 'multipart/form-data': components['schemas']['FunctionDeployBody']\n }\n }\n responses: {\n 201: {\n headers: {\n [name: string]: unknown\n }\n content: {\n 'application/json': components['schemas']['DeployFunctionResponse']\n }\n }\n 403: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n /** @description Failed to deploy function */\n 500: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n }\n }\n 'v1-get-a-function': {\n parameters: {\n query?: never\n header?: never\n path: {\n /** @description Project ref */\n ref: string\n /** @description Function slug */\n function_slug: string\n }\n cookie?: never\n }\n requestBody?: never\n responses: {\n 200: {\n headers: {\n [name: string]: unknown\n }\n content: {\n 'application/json': components['schemas']['FunctionSlugResponse']\n }\n }\n 403: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n /** @description Failed to retrieve function with given slug */\n 500: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n }\n }\n 'v1-delete-a-function': {\n parameters: {\n query?: never\n header?: never\n path: {\n /** @description Project ref */\n ref: string\n /** @description Function slug */\n function_slug: string\n }\n cookie?: never\n }\n requestBody?: never\n responses: {\n 200: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n 403: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n /** @description Failed to delete function with given slug */\n 500: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n }\n }\n 'v1-update-a-function': {\n parameters: {\n query?: {\n slug?: string\n name?: string\n /** @description Boolean string, true or false */\n verify_jwt?: boolean\n /** @description Boolean string, true or false */\n import_map?: boolean\n entrypoint_path?: string\n import_map_path?: string\n }\n header?: never\n path: {\n /** @description Project ref */\n ref: string\n /** @description Function slug */\n function_slug: string\n }\n cookie?: never\n }\n requestBody: {\n content: {\n 'application/vnd.denoland.eszip': string\n 'application/json': components['schemas']['V1UpdateFunctionBody']\n }\n }\n responses: {\n 200: {\n headers: {\n [name: string]: unknown\n }\n content: {\n 'application/json': components['schemas']['FunctionResponse']\n }\n }\n 403: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n /** @description Failed to update function with given slug */\n 500: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n }\n }\n 'v1-get-a-function-body': {\n parameters: {\n query?: never\n header?: never\n path: {\n /** @description Project ref */\n ref: string\n /** @description Function slug */\n function_slug: string\n }\n cookie?: never\n }\n requestBody?: never\n responses: {\n 200: {\n headers: {\n [name: string]: unknown\n }\n content: {\n 'application/json': components['schemas']['StreamableFile']\n }\n }\n 403: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n /** @description Failed to retrieve function body with given slug */\n 500: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n }\n }\n 'v1-list-all-buckets': {\n parameters: {\n query?: never\n header?: never\n path: {\n /** @description Project ref */\n ref: string\n }\n cookie?: never\n }\n requestBody?: never\n responses: {\n 200: {\n headers: {\n [name: string]: unknown\n }\n content: {\n 'application/json': components['schemas']['V1StorageBucketResponse'][]\n }\n }\n 403: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n /** @description Failed to get list of buckets */\n 500: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n }\n }\n 'v1-list-all-sso-provider': {\n parameters: {\n query?: never\n header?: never\n path: {\n /** @description Project ref */\n ref: string\n }\n cookie?: never\n }\n requestBody?: never\n responses: {\n 200: {\n headers: {\n [name: string]: unknown\n }\n content: {\n 'application/json': components['schemas']['ListProvidersResponse']\n }\n }\n 403: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n /** @description SAML 2.0 support is not enabled for this project */\n 404: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n }\n }\n 'v1-create-a-sso-provider': {\n parameters: {\n query?: never\n header?: never\n path: {\n /** @description Project ref */\n ref: string\n }\n cookie?: never\n }\n requestBody: {\n content: {\n 'application/json': components['schemas']['CreateProviderBody']\n }\n }\n responses: {\n 201: {\n headers: {\n [name: string]: unknown\n }\n content: {\n 'application/json': components['schemas']['CreateProviderResponse']\n }\n }\n 403: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n /** @description SAML 2.0 support is not enabled for this project */\n 404: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n }\n }\n 'v1-get-a-sso-provider': {\n parameters: {\n query?: never\n header?: never\n path: {\n /** @description Project ref */\n ref: string\n provider_id: string\n }\n cookie?: never\n }\n requestBody?: never\n responses: {\n 200: {\n headers: {\n [name: string]: unknown\n }\n content: {\n 'application/json': components['schemas']['GetProviderResponse']\n }\n }\n 403: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n /** @description Either SAML 2.0 was not enabled for this project, or the provider does not exist */\n 404: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n }\n }\n 'v1-update-a-sso-provider': {\n parameters: {\n query?: never\n header?: never\n path: {\n /** @description Project ref */\n ref: string\n provider_id: string\n }\n cookie?: never\n }\n requestBody: {\n content: {\n 'application/json': components['schemas']['UpdateProviderBody']\n }\n }\n responses: {\n 200: {\n headers: {\n [name: string]: unknown\n }\n content: {\n 'application/json': components['schemas']['UpdateProviderResponse']\n }\n }\n 403: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n /** @description Either SAML 2.0 was not enabled for this project, or the provider does not exist */\n 404: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n }\n }\n 'v1-delete-a-sso-provider': {\n parameters: {\n query?: never\n header?: never\n path: {\n /** @description Project ref */\n ref: string\n provider_id: string\n }\n cookie?: never\n }\n requestBody?: never\n responses: {\n 200: {\n headers: {\n [name: string]: unknown\n }\n content: {\n 'application/json': components['schemas']['DeleteProviderResponse']\n }\n }\n 403: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n /** @description Either SAML 2.0 was not enabled for this project, or the provider does not exist */\n 404: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n }\n }\n 'v1-list-all-backups': {\n parameters: {\n query?: never\n header?: never\n path: {\n /** @description Project ref */\n ref: string\n }\n cookie?: never\n }\n requestBody?: never\n responses: {\n 200: {\n headers: {\n [name: string]: unknown\n }\n content: {\n 'application/json': components['schemas']['V1BackupsResponse']\n }\n }\n 403: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n /** @description Failed to get backups */\n 500: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n }\n }\n 'v1-restore-pitr-backup': {\n parameters: {\n query?: never\n header?: never\n path: {\n /** @description Project ref */\n ref: string\n }\n cookie?: never\n }\n requestBody: {\n content: {\n 'application/json': components['schemas']['V1RestorePitrBody']\n }\n }\n responses: {\n 201: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n 403: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n }\n }\n 'v1-get-restore-point': {\n parameters: {\n query?: {\n name?: string\n }\n header?: never\n path: {\n /** @description Project ref */\n ref: string\n }\n cookie?: never\n }\n requestBody?: never\n responses: {\n 200: {\n headers: {\n [name: string]: unknown\n }\n content: {\n 'application/json': components['schemas']['V1RestorePointResponse']\n }\n }\n 403: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n /** @description Failed to get requested restore points */\n 500: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n }\n }\n 'v1-create-restore-point': {\n parameters: {\n query?: never\n header?: never\n path: {\n /** @description Project ref */\n ref: string\n }\n cookie?: never\n }\n requestBody: {\n content: {\n 'application/json': components['schemas']['V1RestorePointPostBody']\n }\n }\n responses: {\n 201: {\n headers: {\n [name: string]: unknown\n }\n content: {\n 'application/json': components['schemas']['V1RestorePointResponse']\n }\n }\n }\n }\n 'v1-undo': {\n parameters: {\n query?: never\n header?: never\n path: {\n /** @description Project ref */\n ref: string\n }\n cookie?: never\n }\n requestBody: {\n content: {\n 'application/json': components['schemas']['V1UndoBody']\n }\n }\n responses: {\n 201: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n 403: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n }\n }\n 'v1-list-organization-members': {\n parameters: {\n query?: never\n header?: never\n path: {\n /** @description Organization slug */\n slug: string\n }\n cookie?: never\n }\n requestBody?: never\n responses: {\n 200: {\n headers: {\n [name: string]: unknown\n }\n content: {\n 'application/json': components['schemas']['V1OrganizationMemberResponse'][]\n }\n }\n 403: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n }\n }\n 'v1-get-an-organization': {\n parameters: {\n query?: never\n header?: never\n path: {\n /** @description Organization slug */\n slug: string\n }\n cookie?: never\n }\n requestBody?: never\n responses: {\n 200: {\n headers: {\n [name: string]: unknown\n }\n content: {\n 'application/json': components['schemas']['V1OrganizationSlugResponse']\n }\n }\n 403: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n }\n }\n 'v1-get-organization-project-claim': {\n parameters: {\n query?: never\n header?: never\n path: {\n /** @description Organization slug */\n slug: string\n token: string\n }\n cookie?: never\n }\n requestBody?: never\n responses: {\n 200: {\n headers: {\n [name: string]: unknown\n }\n content: {\n 'application/json': components['schemas']['OrganizationProjectClaimResponse']\n }\n }\n 403: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n }\n }\n 'v1-claim-project-for-organization': {\n parameters: {\n query?: never\n header?: never\n path: {\n /** @description Organization slug */\n slug: string\n token: string\n }\n cookie?: never\n }\n requestBody?: never\n responses: {\n 204: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n 403: {\n headers: {\n [name: string]: unknown\n }\n content?: never\n }\n }\n }\n}\n", + "type": "registry:lib" + }, + { + "path": "registry/default/platform/platform-kit-nextjs/lib/management-api.ts", + "content": "import createClient from 'openapi-fetch'\nimport type { paths } from './management-api-schema'\n\nexport const client = createClient({\n baseUrl: '/api/supabase-proxy',\n})\n", + "type": "registry:lib" + }, + { + "path": "registry/default/platform/platform-kit-nextjs/lib/pg-meta/sql.ts", + "content": "export const columnsSql = `\nSELECT\n c.oid :: int8 AS table_id,\n nc.nspname AS schema,\n c.relname AS table,\n (c.oid || '.' || a.attnum) AS id,\n a.attnum AS ordinal_position,\n a.attname AS name,\n CASE\n WHEN a.atthasdef THEN pg_get_expr(ad.adbin, ad.adrelid)\n ELSE NULL\n END AS default_value,\n CASE\n WHEN t.typtype = 'd' THEN CASE\n WHEN bt.typelem <> 0 :: oid\n AND bt.typlen = -1 THEN 'ARRAY'\n WHEN nbt.nspname = 'pg_catalog' THEN format_type(t.typbasetype, NULL)\n ELSE 'USER-DEFINED'\n END\n ELSE CASE\n WHEN t.typelem <> 0 :: oid\n AND t.typlen = -1 THEN 'ARRAY'\n WHEN nt.nspname = 'pg_catalog' THEN format_type(a.atttypid, NULL)\n ELSE 'USER-DEFINED'\n END\n END AS data_type,\n COALESCE(bt.typname, t.typname) AS format,\n a.attidentity IN ('a', 'd') AS is_identity,\n CASE\n a.attidentity\n WHEN 'a' THEN 'ALWAYS'\n WHEN 'd' THEN 'BY DEFAULT'\n ELSE NULL\n END AS identity_generation,\n a.attgenerated IN ('s') AS is_generated,\n NOT (\n a.attnotnull\n OR t.typtype = 'd' AND t.typnotnull\n ) AS is_nullable,\n (\n c.relkind IN ('r', 'p')\n OR c.relkind IN ('v', 'f') AND pg_column_is_updatable(c.oid, a.attnum, FALSE)\n ) AS is_updatable,\n uniques.table_id IS NOT NULL AS is_unique,\n check_constraints.definition AS \"check\",\n array_to_json(\n array(\n SELECT\n enumlabel\n FROM\n pg_catalog.pg_enum enums\n WHERE\n enums.enumtypid = coalesce(bt.oid, t.oid)\n OR enums.enumtypid = coalesce(bt.typelem, t.typelem)\n ORDER BY\n enums.enumsortorder\n )\n ) AS enums,\n col_description(c.oid, a.attnum) AS comment\nFROM\n pg_attribute a\n LEFT JOIN pg_attrdef ad ON a.attrelid = ad.adrelid\n AND a.attnum = ad.adnum\n JOIN (\n pg_class c\n JOIN pg_namespace nc ON c.relnamespace = nc.oid\n ) ON a.attrelid = c.oid\n JOIN (\n pg_type t\n JOIN pg_namespace nt ON t.typnamespace = nt.oid\n ) ON a.atttypid = t.oid\n LEFT JOIN (\n pg_type bt\n JOIN pg_namespace nbt ON bt.typnamespace = nbt.oid\n ) ON t.typtype = 'd'\n AND t.typbasetype = bt.oid\n LEFT JOIN (\n SELECT DISTINCT ON (table_id, ordinal_position)\n conrelid AS table_id,\n conkey[1] AS ordinal_position\n FROM pg_catalog.pg_constraint\n WHERE contype = 'u' AND cardinality(conkey) = 1\n ) AS uniques ON uniques.table_id = c.oid AND uniques.ordinal_position = a.attnum\n LEFT JOIN (\n -- We only select the first column check\n SELECT DISTINCT ON (table_id, ordinal_position)\n conrelid AS table_id,\n conkey[1] AS ordinal_position,\n substring(\n pg_get_constraintdef(pg_constraint.oid, true),\n 8,\n length(pg_get_constraintdef(pg_constraint.oid, true)) - 8\n ) AS \"definition\"\n FROM pg_constraint\n WHERE contype = 'c' AND cardinality(conkey) = 1\n ORDER BY table_id, ordinal_position, oid asc\n ) AS check_constraints ON check_constraints.table_id = c.oid AND check_constraints.ordinal_position = a.attnum\nWHERE\n NOT pg_is_other_temp_schema(nc.oid)\n AND a.attnum > 0\n AND NOT a.attisdropped\n AND (c.relkind IN ('r', 'v', 'm', 'f', 'p'))\n AND (\n pg_has_role(c.relowner, 'USAGE')\n OR has_column_privilege(\n c.oid,\n a.attnum,\n 'SELECT, INSERT, UPDATE, REFERENCES'\n )\n )\n`\n\nexport const tablesSql = `\nSELECT\n c.oid :: int8 AS id,\n nc.nspname AS schema,\n c.relname AS name,\n c.relrowsecurity AS rls_enabled,\n c.relforcerowsecurity AS rls_forced,\n CASE\n WHEN c.relreplident = 'd' THEN 'DEFAULT'\n WHEN c.relreplident = 'i' THEN 'INDEX'\n WHEN c.relreplident = 'f' THEN 'FULL'\n ELSE 'NOTHING'\n END AS replica_identity,\n pg_total_relation_size(format('%I.%I', nc.nspname, c.relname)) :: int8 AS bytes,\n pg_size_pretty(\n pg_total_relation_size(format('%I.%I', nc.nspname, c.relname))\n ) AS size,\n pg_stat_get_live_tuples(c.oid) AS live_rows_estimate,\n pg_stat_get_dead_tuples(c.oid) AS dead_rows_estimate,\n obj_description(c.oid) AS comment,\n coalesce(pk.primary_keys, '[]') as primary_keys,\n coalesce(\n jsonb_agg(relationships) filter (where relationships is not null),\n '[]'\n ) as relationships\nFROM\n pg_namespace nc\n JOIN pg_class c ON nc.oid = c.relnamespace\n left join (\n select\n table_id,\n jsonb_agg(_pk.*) as primary_keys\n from (\n select\n n.nspname as schema,\n c.relname as table_name,\n a.attname as name,\n c.oid :: int8 as table_id\n from\n pg_index i,\n pg_class c,\n pg_attribute a,\n pg_namespace n\n where\n i.indrelid = c.oid\n and c.relnamespace = n.oid\n and a.attrelid = c.oid\n and a.attnum = any (i.indkey)\n and i.indisprimary\n ) as _pk\n group by table_id\n ) as pk\n on pk.table_id = c.oid\n left join (\n select\n c.oid :: int8 as id,\n c.conname as constraint_name,\n nsa.nspname as source_schema,\n csa.relname as source_table_name,\n sa.attname as source_column_name,\n nta.nspname as target_table_schema,\n cta.relname as target_table_name,\n ta.attname as target_column_name\n from\n pg_constraint c\n join (\n pg_attribute sa\n join pg_class csa on sa.attrelid = csa.oid\n join pg_namespace nsa on csa.relnamespace = nsa.oid\n ) on sa.attrelid = c.conrelid and sa.attnum = any (c.conkey)\n join (\n pg_attribute ta\n join pg_class cta on ta.attrelid = cta.oid\n join pg_namespace nta on cta.relnamespace = nta.oid\n ) on ta.attrelid = c.confrelid and ta.attnum = any (c.confkey)\n where\n c.contype = 'f'\n ) as relationships\n on (relationships.source_schema = nc.nspname and relationships.source_table_name = c.relname)\n or (relationships.target_table_schema = nc.nspname and relationships.target_table_name = c.relname)\nWHERE\n c.relkind IN ('r', 'p')\n AND NOT pg_is_other_temp_schema(nc.oid)\n AND (\n pg_has_role(c.relowner, 'USAGE')\n OR has_table_privilege(\n c.oid,\n 'SELECT, INSERT, UPDATE, DELETE, TRUNCATE, REFERENCES, TRIGGER'\n )\n OR has_any_column_privilege(c.oid, 'SELECT, INSERT, UPDATE, REFERENCES')\n )\ngroup by\n c.oid,\n c.relname,\n c.relrowsecurity,\n c.relforcerowsecurity,\n c.relreplident,\n nc.nspname,\n pk.primary_keys\n`\n\nexport const extensionsSql = `\nSELECT\n e.name,\n n.nspname AS schema,\n e.default_version,\n x.extversion AS installed_version,\n e.comment\nFROM\n pg_available_extensions() e(name, default_version, comment)\n LEFT JOIN pg_extension x ON e.name = x.extname\n LEFT JOIN pg_namespace n ON x.extnamespace = n.oid\n`\n", + "type": "registry:lib" + }, + { + "path": "registry/default/platform/platform-kit-nextjs/lib/pg-meta/index.ts", + "content": "import { stripIndent } from 'common-tags'\n\nimport { columnsSql, extensionsSql, tablesSql } from './sql'\n\nexport const SYSTEM_SCHEMAS = [\n 'information_schema',\n 'pg_catalog',\n 'pg_toast',\n '_timescaledb_internal',\n]\n\n/**\n * Generates the SQL query to list tables in the database.\n */\nexport function listTablesSql(schemas: string[] = []) {\n let sql = stripIndent`\n with\n tables as (${tablesSql}),\n columns as (${columnsSql})\n select\n *,\n ${coalesceRowsToArray('columns', 'columns.table_id = tables.id')}\n from tables\n `\n\n sql += '\\n'\n\n if (schemas.length > 0) {\n sql += `where schema in (${schemas.map((s) => `'${s}'`).join(',')})`\n } else {\n sql += `where schema not in (${SYSTEM_SCHEMAS.map((s) => `'${s}'`).join(',')})`\n }\n\n return sql\n}\n\n/**\n * Generates the SQL query to list all extensions in the database.\n */\nexport function listExtensionsSql() {\n return extensionsSql\n}\n\n/**\n * Generates a SQL segment that coalesces rows into an array of JSON objects.\n */\nexport const coalesceRowsToArray = (source: string, filter: string) => {\n return stripIndent`\n COALESCE(\n (\n SELECT\n array_agg(row_to_json(${source})) FILTER (WHERE ${filter})\n FROM\n ${source}\n ),\n '{}'\n ) AS ${source}\n `\n}\n", + "type": "registry:lib" + }, + { + "path": "registry/default/platform/platform-kit-nextjs/lib/pg-meta/types.ts", + "content": "import { z } from 'zod'\n\nexport const postgresPrimaryKeySchema = z.object({\n schema: z.string(),\n table_name: z.string(),\n name: z.string(),\n table_id: z.number().int(),\n})\n\nexport const postgresRelationshipSchema = z.object({\n id: z.number().int(),\n constraint_name: z.string(),\n source_schema: z.string(),\n source_table_name: z.string(),\n source_column_name: z.string(),\n target_table_schema: z.string(),\n target_table_name: z.string(),\n target_column_name: z.string(),\n})\n\nexport const postgresColumnSchema = z.object({\n table_id: z.number().int(),\n schema: z.string(),\n table: z.string(),\n id: z.string().regex(/^(\\d+)\\.(\\d+)$/),\n ordinal_position: z.number().int(),\n name: z.string(),\n default_value: z.any(),\n data_type: z.string(),\n format: z.string(),\n is_identity: z.boolean(),\n identity_generation: z.union([z.literal('ALWAYS'), z.literal('BY DEFAULT'), z.null()]),\n is_generated: z.boolean(),\n is_nullable: z.boolean(),\n is_updatable: z.boolean(),\n is_unique: z.boolean(),\n enums: z.array(z.string()),\n check: z.union([z.string(), z.null()]),\n comment: z.union([z.string(), z.null()]),\n})\n\nexport const postgresTableSchema = z.object({\n id: z.number().int(),\n schema: z.string(),\n name: z.string(),\n rls_enabled: z.boolean(),\n rls_forced: z.boolean(),\n replica_identity: z.union([\n z.literal('DEFAULT'),\n z.literal('INDEX'),\n z.literal('FULL'),\n z.literal('NOTHING'),\n ]),\n bytes: z.number().int(),\n size: z.string(),\n live_rows_estimate: z.number().int(),\n dead_rows_estimate: z.number().int(),\n comment: z.string().nullable(),\n columns: z.array(postgresColumnSchema).optional(),\n primary_keys: z.array(postgresPrimaryKeySchema),\n relationships: z.array(postgresRelationshipSchema),\n})\n\nexport const postgresExtensionSchema = z.object({\n name: z.string(),\n schema: z.union([z.string(), z.null()]),\n default_version: z.string(),\n installed_version: z.union([z.string(), z.null()]),\n comment: z.union([z.string(), z.null()]),\n})\n\nexport type PostgresPrimaryKey = z.infer\nexport type PostgresRelationship = z.infer\nexport type PostgresColumn = z.infer\nexport type PostgresTable = z.infer\nexport type PostgresExtension = z.infer\n", + "type": "registry:lib" + }, + { + "path": "registry/default/platform/platform-kit-nextjs/lib/schemas/auth.ts", + "content": "import { z } from 'zod'\n\n// Field labels mapping for form generation\nexport const authFieldLabels: Record<\n string,\n string | { label: string; options?: Record }\n> = {\n // General Settings\n disable_signup: 'Disable Signup',\n external_anonymous_users_enabled: 'Allow Anonymous Sign-ins',\n\n // Email Provider\n external_email_enabled: 'Enable Email Provider',\n mailer_autoconfirm: 'Confirm Email',\n mailer_secure_email_change_enabled: 'Secure Email Change',\n security_update_password_require_reauthentication: 'Secure Password Change',\n password_hibp_enabled: 'Prevent Leaked Passwords',\n password_min_length: 'Minimum Password Length',\n password_required_characters: {\n label: 'Password Requirements',\n options: {\n NO_REQUIRED_CHARS: 'No requirements',\n 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ:0123456789': 'Letters and numbers',\n 'abcdefghijklmnopqrstuvwxyz:ABCDEFGHIJKLMNOPQRSTUVWXYZ:0123456789':\n 'Lowercase, uppercase, and numbers',\n 'abcdefghijklmnopqrstuvwxyz:ABCDEFGHIJKLMNOPQRSTUVWXYZ:0123456789:!@#$%^&*()_+-=[]{};\\'\\\\\\\\:\"|<>?,./`~':\n 'Letters, numbers, and symbols',\n },\n },\n mailer_otp_exp: 'Email OTP Expiration (seconds)',\n mailer_otp_length: 'Email OTP Length',\n\n // Phone Provider\n external_phone_enabled: 'Enable Phone Provider',\n sms_provider: {\n label: 'SMS Provider',\n options: {\n twilio: 'Twilio',\n messagebird: 'MessageBird',\n textlocal: 'TextLocal',\n vonage: 'Vonage',\n twilio_verify: 'Twilio Verify',\n },\n },\n sms_twilio_account_sid: 'Twilio Account SID',\n sms_twilio_auth_token: 'Twilio Auth Token',\n sms_twilio_message_service_sid: 'Twilio Message Service SID',\n sms_twilio_content_sid: 'Twilio Content SID',\n sms_twilio_verify_account_sid: 'Twilio Verify Account SID',\n sms_twilio_verify_auth_token: 'Twilio Verify Auth Token',\n sms_twilio_verify_message_service_sid: 'Twilio Verify Message Service SID',\n sms_messagebird_access_key: 'MessageBird Access Key',\n sms_messagebird_originator: 'MessageBird Originator',\n sms_textlocal_api_key: 'TextLocal API Key',\n sms_textlocal_sender: 'TextLocal Sender',\n sms_vonage_api_key: 'Vonage API Key',\n sms_vonage_api_secret: 'Vonage API Secret',\n sms_vonage_from: 'Vonage From Number',\n sms_autoconfirm: 'Enable Phone Confirmations',\n sms_otp_exp: 'SMS OTP Expiry (seconds)',\n sms_otp_length: 'SMS OTP Length',\n sms_template: 'SMS Message Template',\n sms_test_otp: 'Test Phone Numbers and OTPs',\n sms_test_otp_valid_until: 'Test OTPs Valid Until',\n\n // Google Provider\n external_google_enabled: 'Enable Google Sign-in',\n external_google_client_id: 'Google Client ID(s)',\n external_google_secret: 'Google OAuth Client Secret',\n external_google_skip_nonce_check: 'Skip Nonce Check',\n\n // Secrets\n name: 'Secret Name',\n value: 'Secret Value',\n secretNames: 'Secret Names',\n}\n\n// New schemas for auth settings\n\nexport const authGeneralSettingsSchema = z.object({\n disable_signup: z.boolean().optional().describe('This will prevent new users from signing up.'),\n external_anonymous_users_enabled: z\n .boolean()\n .optional()\n .describe('This will enable anonymous users to sign in to your application.'),\n})\nexport type AuthGeneralSettingsSchema = z.infer\n\nconst NO_REQUIRED_CHARACTERS = 'NO_REQUIRED_CHARS'\n\nexport const authEmailProviderSchema = z\n .object({\n external_email_enabled: z\n .boolean()\n .optional()\n .describe('This will enable Email based signup and login for your application.'),\n mailer_autoconfirm: z\n .boolean()\n .optional()\n .describe(\n 'Users will need to confirm their email address before signing in for the first time.'\n ),\n mailer_secure_email_change_enabled: z\n .boolean()\n .optional()\n .describe(\n 'Users will be required to confirm any email change on both the old email address and new email address. If disabled, only the new email is required to confirm.'\n ),\n security_update_password_require_reauthentication: z\n .boolean()\n .optional()\n .describe(\n 'Users will need to be recently logged in to change their password without requiring reauthentication. (A user is considered recently logged in if the session was created within the last 24 hours.) If disabled, a user can change their password at any time.'\n ),\n password_hibp_enabled: z\n .boolean()\n .optional()\n .describe(\n 'Rejects the use of known or easy to guess passwords on sign up or password change. Powered by the HaveIBeenPwned.org Pwned Passwords API.'\n ),\n password_min_length: z\n .number()\n .int()\n .min(6, 'Must be greater or equal to 6.')\n .describe(\n 'Passwords shorter than this value will be rejected as weak. Minimum 6, recommended 8 or more.'\n ),\n password_required_characters: z\n .preprocess(\n (val) => (val === '' || val === null || val === undefined ? NO_REQUIRED_CHARACTERS : val),\n z.enum([\n NO_REQUIRED_CHARACTERS,\n 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ:0123456789',\n 'abcdefghijklmnopqrstuvwxyz:ABCDEFGHIJKLMNOPQRSTUVWXYZ:0123456789',\n 'abcdefghijklmnopqrstuvwxyz:ABCDEFGHIJKLMNOPQRSTUVWXYZ:0123456789:!@#$%^&*()_+-=[]{};\\'\\\\\\\\:\"|<>?,./`~',\n ])\n )\n .optional()\n .transform((val) => (val === NO_REQUIRED_CHARACTERS ? '' : val))\n .describe('Passwords that do not have at least one of each will be rejected as weak.'),\n mailer_otp_exp: z\n .number()\n .int()\n .min(0, 'Must be more than 0')\n .max(86400, 'Must be no more than 86400')\n .describe('Duration before an email otp / link expires in seconds.'),\n mailer_otp_length: z\n .number()\n .int()\n .min(6, 'Must be at least 6')\n .max(10, 'Must be no more than 10')\n .optional()\n .describe('Number of digits in the email OTP'),\n })\n .describe('Email provider settings.')\nexport type AuthEmailProviderSchema = z.infer\n\nexport const authPhoneProviderSchema = z\n .object({\n external_phone_enabled: z\n .boolean()\n .optional()\n .describe('This will enable phone based login for your application'),\n sms_provider: z\n .enum(['twilio', 'messagebird', 'textlocal', 'vonage', 'twilio_verify'])\n .optional()\n .describe('External provider that will handle sending SMS messages'),\n // Twilio\n sms_twilio_account_sid: z.string().optional(),\n sms_twilio_auth_token: z.string().optional(),\n sms_twilio_message_service_sid: z.string().optional(),\n sms_twilio_content_sid: z\n .string()\n .optional()\n .describe('Twilio Content SID (Optional, For WhatsApp Only)'),\n // Twilio Verify\n sms_twilio_verify_account_sid: z.string().optional(),\n sms_twilio_verify_auth_token: z.string().optional(),\n sms_twilio_verify_message_service_sid: z.string().optional(),\n // Messagebird\n sms_messagebird_access_key: z.string().optional(),\n sms_messagebird_originator: z.string().optional(),\n // Textlocal\n sms_textlocal_api_key: z.string().optional(),\n sms_textlocal_sender: z.string().optional(),\n // Vonage\n sms_vonage_api_key: z.string().optional(),\n sms_vonage_api_secret: z.string().optional(),\n sms_vonage_from: z.string().optional(),\n // SMS Confirm settings\n sms_autoconfirm: z\n .boolean()\n .optional()\n .describe('Users will need to confirm their phone number before signing in.'),\n sms_otp_exp: z\n .number()\n .int()\n .optional()\n .describe('Duration before an SMS OTP expires in seconds.'),\n sms_otp_length: z.number().int().optional().describe('Number of digits in OTP.'),\n sms_template: z.string().optional().describe('To format the OTP code use `{{ .Code }}`'),\n sms_test_otp: z\n .string()\n .optional()\n .describe(\n 'Register phone number and OTP combinations for testing as a comma separated list of = pairs. Example: `18005550123=789012`'\n ),\n sms_test_otp_valid_until: z\n .string()\n .datetime({ message: 'Invalid datetime string.' })\n .optional()\n .describe(\n \"Test phone number and OTP combinations won't be active past this date and time (local time zone).\"\n ),\n })\n .describe('Phone provider settings.')\nexport type AuthPhoneProviderSchema = z.infer\n\nexport const authGoogleProviderObject = z.object({\n external_google_enabled: z.boolean().optional().describe('Enable Sign in with Google'),\n external_google_client_id: z\n .string()\n .regex(\n /^([a-z0-9-]+\\.[a-z0-9-]+(\\.[a-z0-9-]+)*(,[a-z0-9-]+\\.[a-z0-9-]+(\\.[a-z0-9-]+)*)*)$/i,\n 'Google Client IDs should be a comma-separated list of domain-like strings.'\n )\n .optional(),\n external_google_secret: z\n .string()\n .regex(\n /^[a-z0-9.\\/_-]*$/i,\n 'Google OAuth Client Secrets usually contain letters, numbers, dots, dashes, and underscores.'\n )\n .optional(),\n external_google_skip_nonce_check: z\n .boolean()\n .optional()\n .describe('Allows ID tokens with any nonce to be accepted, which is less secure.'),\n})\n\nexport const authGoogleProviderSchema = authGoogleProviderObject\n .superRefine((data, ctx) => {\n if (data.external_google_enabled && !data.external_google_client_id) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n path: ['external_google_client_id'],\n message: 'At least one Client ID is required when Google sign-in is enabled.',\n })\n }\n if (data.external_google_client_id && data.external_google_client_id.includes(' ')) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n path: ['external_google_client_id'],\n message: 'Client IDs should not contain spaces.',\n })\n }\n })\n .describe('Google provider settings.')\nexport type AuthGoogleProviderSchema = z.infer\n\nexport const authConfigUpdateSchema = authGeneralSettingsSchema\n .merge(authEmailProviderSchema)\n .merge(authPhoneProviderSchema)\n .merge(authGoogleProviderObject)\n\nexport type AuthConfigUpdateSchema = z.infer\n\n// A version used for partial updates (all fields optional)\nexport const authConfigUpdatePayloadSchema = authConfigUpdateSchema.partial()\n", + "type": "registry:lib" + }, + { + "path": "registry/default/platform/platform-kit-nextjs/lib/schemas/secrets.ts", + "content": "import z from 'zod'\n\nexport const secretsSchema = z\n .object({\n secrets: z.array(\n z.object({\n name: z\n .string()\n .min(1, 'Secret name is required.')\n .regex(\n /^[a-zA-Z_][a-zA-Z0-9_]*$/,\n 'Must contain letters, numbers, and underscores, starting with a letter or underscore.'\n ),\n value: z.string().min(1, 'Secret value is required.'),\n })\n ),\n })\n .describe('Secrets schema for managing environment variables.')\n\nexport type SecretsSchema = z.infer\n\nexport const deleteSecretsSchema = z\n .object({\n secretNames: z\n .array(z.string().min(1, 'Secret name cannot be empty.'))\n .min(1, 'At least one secret name is required.'),\n })\n .describe('Schema for deleting secrets by name.')\nexport type DeleteSecretsSchema = z.infer\n", + "type": "registry:lib" + }, + { + "path": "registry/default/platform/platform-kit-nextjs/contexts/SheetNavigationContext.tsx", + "content": "'use client'\n\nimport { createContext, useContext, useState, ReactNode, useMemo, useCallback } from 'react'\n\ntype StackItem = {\n title: string\n component: ReactNode\n}\n\ntype SheetNavigationContextType = {\n stack: StackItem[]\n push: (item: StackItem) => void\n pop: () => void\n popTo: (index: number) => void\n reset: () => void\n}\n\nconst SheetNavigationContext = createContext(undefined)\n\nexport function SheetNavigationProvider({\n children,\n onStackEmpty,\n initialStack = [],\n}: {\n children: ReactNode\n onStackEmpty?: () => void\n initialStack?: StackItem[]\n}) {\n const [stack, setStack] = useState(initialStack)\n\n const push = useCallback((item: StackItem) => {\n setStack((prev) => [...prev, item])\n }, [])\n\n const pop = useCallback(() => {\n setStack((prev) => {\n const newStack = prev.slice(0, -1)\n if (newStack.length === 0) {\n onStackEmpty?.()\n }\n return newStack\n })\n }, [onStackEmpty])\n\n const popTo = useCallback((index: number) => {\n if (index < 0) return\n setStack((prev) => prev.slice(0, index + 1))\n }, [])\n\n const reset = useCallback(() => {\n setStack([])\n onStackEmpty?.()\n }, [onStackEmpty])\n\n const value = useMemo(\n () => ({ stack, push, pop, popTo, reset }),\n [stack, push, pop, popTo, reset]\n )\n\n return {children}\n}\n\nexport function useSheetNavigation() {\n const context = useContext(SheetNavigationContext)\n if (context === undefined) {\n throw new Error('useSheetNavigation must be used within a SheetNavigationProvider')\n }\n return context\n}\n", + "type": "registry:component", + "target": "contexts/SheetNavigationContext.tsx" + } + ] +} \ No newline at end of file diff --git a/apps/ui-library/registry.json b/apps/ui-library/registry.json index 22379b2828e..a7458fc4414 100644 --- a/apps/ui-library/registry.json +++ b/apps/ui-library/registry.json @@ -1210,6 +1210,183 @@ } ] }, + { + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "platform-kit-nextjs", + "type": "registry:block", + "title": "Platform Kit for Nextjs and Supabase", + "description": "Platform Kit for Nextjs and Supabase", + "registryDependencies": [ + "alert", + "badge", + "button", + "card", + "chart", + "command", + "dialog", + "drawer", + "form", + "hover-card", + "input", + "label", + "popover", + "select", + "skeleton", + "switch", + "table", + "toggle", + "toggle-group", + "tooltip" + ], + "dependencies": [ + "@hookform/resolvers", + "@monaco-editor/react", + "@tanstack/react-query", + "@tanstack/react-table", + "axios", + "cmdk", + "common-tags", + "openai", + "openapi-fetch", + "react-hook-form", + "react-markdown", + "recharts", + "sonner" + ], + "files": [ + { + "path": "registry/default/platform/platform-kit-nextjs/app/api/ai/sql/route.ts", + "type": "registry:page", + "target": "app/api/ai/sql/route.ts" + }, + { + "path": "registry/default/platform/platform-kit-nextjs/app/api/supabase-proxy/[...path]/route.ts", + "type": "registry:page", + "target": "app/api/supabase-proxy/[...path]/route.ts" + }, + { + "path": "registry/default/platform/platform-kit-nextjs/components/dynamic-form.tsx", + "type": "registry:component" + }, + { + "path": "registry/default/platform/platform-kit-nextjs/components/logo-supabase.tsx", + "type": "registry:component" + }, + { + "path": "registry/default/platform/platform-kit-nextjs/components/results-table.tsx", + "type": "registry:component" + }, + { + "path": "registry/default/platform/platform-kit-nextjs/components/sql-editor.tsx", + "type": "registry:component" + }, + { + "path": "registry/default/platform/platform-kit-nextjs/components/supabase-manager/auth.tsx", + "type": "registry:component" + }, + { + "path": "registry/default/platform/platform-kit-nextjs/components/supabase-manager/database.tsx", + "type": "registry:component" + }, + { + "path": "registry/default/platform/platform-kit-nextjs/components/supabase-manager/index.tsx", + "type": "registry:component" + }, + { + "path": "registry/default/platform/platform-kit-nextjs/components/supabase-manager/logs.tsx", + "type": "registry:component" + }, + { + "path": "registry/default/platform/platform-kit-nextjs/components/supabase-manager/secrets.tsx", + "type": "registry:component" + }, + { + "path": "registry/default/platform/platform-kit-nextjs/components/supabase-manager/storage.tsx", + "type": "registry:component" + }, + { + "path": "registry/default/platform/platform-kit-nextjs/components/supabase-manager/suggestions.tsx", + "type": "registry:component" + }, + { + "path": "registry/default/platform/platform-kit-nextjs/components/supabase-manager/users.tsx", + "type": "registry:component" + }, + { + "path": "registry/default/platform/platform-kit-nextjs/components/users-growth-chart.tsx", + "type": "registry:component" + }, + { + "path": "registry/default/platform/platform-kit-nextjs/hooks/use-auth.ts", + "type": "registry:hook" + }, + { + "path": "registry/default/platform/platform-kit-nextjs/hooks/use-logs.ts", + "type": "registry:hook" + }, + { + "path": "registry/default/platform/platform-kit-nextjs/hooks/use-run-query.ts", + "type": "registry:hook" + }, + { + "path": "registry/default/platform/platform-kit-nextjs/hooks/use-secrets.ts", + "type": "registry:hook" + }, + { + "path": "registry/default/platform/platform-kit-nextjs/hooks/use-storage.ts", + "type": "registry:hook" + }, + { + "path": "registry/default/platform/platform-kit-nextjs/hooks/use-suggestions.ts", + "type": "registry:hook" + }, + { + "path": "registry/default/platform/platform-kit-nextjs/hooks/use-tables.ts", + "type": "registry:hook" + }, + { + "path": "registry/default/platform/platform-kit-nextjs/hooks/use-user-counts.ts", + "type": "registry:hook" + }, + { + "path": "registry/default/platform/platform-kit-nextjs/lib/logs.ts", + "type": "registry:lib" + }, + { + "path": "registry/default/platform/platform-kit-nextjs/lib/management-api-schema.d.ts", + "type": "registry:lib" + }, + { + "path": "registry/default/platform/platform-kit-nextjs/lib/management-api.ts", + "type": "registry:lib" + }, + { + "path": "registry/default/platform/platform-kit-nextjs/lib/pg-meta/sql.ts", + "type": "registry:lib" + }, + { + "path": "registry/default/platform/platform-kit-nextjs/lib/pg-meta/index.ts", + "type": "registry:lib" + }, + { + "path": "registry/default/platform/platform-kit-nextjs/lib/pg-meta/types.ts", + "type": "registry:lib" + }, + { + "path": "registry/default/platform/platform-kit-nextjs/lib/schemas/auth.ts", + "type": "registry:lib" + }, + { + "path": "registry/default/platform/platform-kit-nextjs/lib/schemas/secrets.ts", + "type": "registry:lib" + }, + { + "path": "registry/default/platform/platform-kit-nextjs/contexts/SheetNavigationContext.tsx", + "type": "registry:component", + "target": "contexts/SheetNavigationContext.tsx" + } + ] + }, { "$schema": "https://ui.shadcn.com/schema/registry-item.json", "name": "ai-editor-rules", diff --git a/apps/ui-library/registry/default/components/ui/alert.tsx b/apps/ui-library/registry/default/components/ui/alert.tsx new file mode 100644 index 00000000000..e9c7035408d --- /dev/null +++ b/apps/ui-library/registry/default/components/ui/alert.tsx @@ -0,0 +1,52 @@ +import * as React from 'react' +import { cva, type VariantProps } from 'class-variance-authority' + +import { cn } from '@/lib/utils' + +const alertVariants = cva( + 'relative w-full rounded-lg border p-8 text-center [&>svg]:mx-auto [&>svg]:mb-4 [&>svg]:text-foreground', + { + variants: { + variant: { + default: 'bg-background text-foreground', + destructive: 'bg-background text-foreground [&>svg]:text-destructive', + }, + }, + defaultVariants: { + variant: 'default', + }, + } +) + +const Alert = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes & VariantProps +>(({ className, variant, ...props }, ref) => ( +
+)) +Alert.displayName = 'Alert' + +const AlertTitle = React.forwardRef>( + ({ className, ...props }, ref) => ( +
+ ) +) +AlertTitle.displayName = 'AlertTitle' + +const AlertDescription = React.forwardRef< + HTMLParagraphElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)) +AlertDescription.displayName = 'AlertDescription' + +export { Alert, AlertTitle, AlertDescription } diff --git a/apps/ui-library/registry/default/components/ui/badge.tsx b/apps/ui-library/registry/default/components/ui/badge.tsx new file mode 100644 index 00000000000..fa899d9a365 --- /dev/null +++ b/apps/ui-library/registry/default/components/ui/badge.tsx @@ -0,0 +1,33 @@ +import * as React from 'react' +import { cva, type VariantProps } from 'class-variance-authority' + +import { cn } from '@/lib/utils' + +const badgeVariants = cva( + 'inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-hidden focus:ring-2 focus:ring-ring focus:ring-offset-2', + { + variants: { + variant: { + default: 'border-transparent bg-primary text-primary-foreground hover:bg-primary/80', + secondary: + 'border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80', + destructive: + 'border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80', + outline: 'text-foreground', + }, + }, + defaultVariants: { + variant: 'default', + }, + } +) + +export interface BadgeProps + extends React.HTMLAttributes, + VariantProps {} + +function Badge({ className, variant, ...props }: BadgeProps) { + return
+} + +export { Badge, badgeVariants } diff --git a/apps/ui-library/registry/default/components/ui/chart.tsx b/apps/ui-library/registry/default/components/ui/chart.tsx new file mode 100644 index 00000000000..37ee71b4552 --- /dev/null +++ b/apps/ui-library/registry/default/components/ui/chart.tsx @@ -0,0 +1,326 @@ +'use client' + +import * as React from 'react' +import * as RechartsPrimitive from 'recharts' + +import { cn } from '@/lib/utils' + +// Format: { THEME_NAME: CSS_SELECTOR } +const THEMES = { light: '', dark: '.dark' } as const + +export type ChartConfig = { + [k in string]: { + label?: React.ReactNode + icon?: React.ComponentType + } & ( + | { color?: string; theme?: never } + | { color?: never; theme: Record } + ) +} + +type ChartContextProps = { + config: ChartConfig +} + +const ChartContext = React.createContext(null) + +function useChart() { + const context = React.useContext(ChartContext) + + if (!context) { + throw new Error('useChart must be used within a ') + } + + return context +} + +const ChartContainer = React.forwardRef< + HTMLDivElement, + React.ComponentProps<'div'> & { + config: ChartConfig + children: React.ComponentProps['children'] + } +>(({ id, className, children, config, ...props }, ref) => { + const uniqueId = React.useId() + const chartId = `chart-${id || uniqueId.replace(/:/g, '')}` + + return ( + +
+ + {children} +
+
+ ) +}) +ChartContainer.displayName = 'Chart' + +const ChartStyle = ({ id, config }: { id: string; config: ChartConfig }) => { + const colorConfig = Object.entries(config).filter(([_, config]) => config.theme || config.color) + + if (!colorConfig.length) { + return null + } + + return ( +