From 43300d43ce653a3d4aba6e3070b9ceb700f46fa5 Mon Sep 17 00:00:00 2001
From: Gildas Garcia <1122076+djhi@users.noreply.github.com>
Date: Tue, 9 Jun 2026 15:49:10 +0200
Subject: [PATCH] chore: consolidate `useAPIKeysQuery` + `getKeys` into a
single `useAPIKeys` hook (#46761)
## Problem
- API may return a non-array shape that can crash `getKeys` because of
an hard coded cast
- getting API keys is cumbersome as consumers have to call two functions
## Solution
- consolidate `useAPIKeysQuery` + `getKeys` into a single `useAPIKeys`
hook
- guard `getKeys` so that it doesn't crash if passed a non array value
- update usages
## Summary by CodeRabbit
* **Refactor**
* Unified how project API keys are retrieved across the studio,
resulting in more consistent loading/error handling and slight
responsiveness improvements when showing keys and related command
snippets. UI and permissions behavior remain unchanged for end users.
---
.../interfaces/App/CommandMenu/ApiKeys.tsx | 8 ++---
.../interfaces/ConnectSheet/ConnectSheet.tsx | 10 +++----
.../interfaces/Database/Hooks/HTTPHeaders.tsx | 12 ++++----
.../DestinationPanelFields.tsx | 6 ++--
.../DestinationForm/index.tsx | 7 +++--
.../interfaces/Docs/Authentication.tsx | 7 +++--
.../EdgeFunctionDetails.tsx | 6 ++--
.../EdgeFunctionTesterSheet.tsx | 6 ++--
.../Functions/TerminalInstructions.tsx | 6 ++--
.../Home/NewProjectPanel/APIKeys.tsx | 8 ++---
.../CronJobs/HttpHeaderFieldsSection.tsx | 6 ++--
.../ProjectAPIDocs/Content/Introduction.tsx | 7 +++--
.../ProjectAPIDocs/ProjectAPIDocs.tsx | 6 ++--
.../ProjectHome/ProjectConnectionPopover.tsx | 6 ++--
.../Inspector/RealtimeTokensPopover.tsx | 9 +++---
.../VectorBucketTableExamplesSheet.tsx | 8 ++---
apps/studio/data/api-keys/api-keys-query.ts | 30 +++++++++++++++++++
.../iceberg-wrapper-create-mutation.ts | 7 ++---
18 files changed, 93 insertions(+), 62 deletions(-)
diff --git a/apps/studio/components/interfaces/App/CommandMenu/ApiKeys.tsx b/apps/studio/components/interfaces/App/CommandMenu/ApiKeys.tsx
index d223aa14c4b..82fe7b2cbb2 100644
--- a/apps/studio/components/interfaces/App/CommandMenu/ApiKeys.tsx
+++ b/apps/studio/components/interfaces/App/CommandMenu/ApiKeys.tsx
@@ -15,7 +15,7 @@ import {
import { COMMAND_MENU_SECTIONS } from './CommandMenu.utils'
import { orderCommandSectionsByPriority } from './ordering'
-import { getKeys, useAPIKeysQuery } from '@/data/api-keys/api-keys-query'
+import { useAPIKeys } from '@/data/api-keys/api-keys-query'
import { useAsyncCheckPermissions } from '@/hooks/misc/useCheckPermissions'
import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject'
@@ -31,13 +31,13 @@ export function useApiKeysCommands() {
const { can: canReadAPIKeys } = useAsyncCheckPermissions(PermissionAction.SECRETS_READ, '*')
- const { data: apiKeys } = useAPIKeysQuery(
+ const { data: apiKeysData } = useAPIKeys(
{ projectRef: project?.ref, reveal: true },
{ enabled: canReadAPIKeys }
)
const commands = useMemo(() => {
const { anonKey, serviceKey, publishableKey, allSecretKeys } = canReadAPIKeys
- ? getKeys(apiKeys)
+ ? (apiKeysData ?? {})
: {}
return [
@@ -127,7 +127,7 @@ export function useApiKeysCommands() {
icon: () => ,
},
].filter(Boolean) as ICommand[]
- }, [apiKeys, canReadAPIKeys, project, ref, resetCommandMenu, setIsOpen])
+ }, [canReadAPIKeys, apiKeysData, project, ref, resetCommandMenu, setIsOpen])
useRegisterPage(
API_KEYS_PAGE_NAME,
diff --git a/apps/studio/components/interfaces/ConnectSheet/ConnectSheet.tsx b/apps/studio/components/interfaces/ConnectSheet/ConnectSheet.tsx
index 01e5a58f318..994371b3b29 100644
--- a/apps/studio/components/interfaces/ConnectSheet/ConnectSheet.tsx
+++ b/apps/studio/components/interfaces/ConnectSheet/ConnectSheet.tsx
@@ -12,7 +12,7 @@ import { useAvailableConnectModes } from './useAvailableConnectModes'
import { useConnectSheetParams } from './useConnectSheetParams'
import { useConnectSheetShortcut } from './useConnectSheetShortcut'
import { useConnectState } from './useConnectState'
-import { getKeys, useAPIKeysQuery } from '@/data/api-keys/api-keys-query'
+import { useAPIKeys } from '@/data/api-keys/api-keys-query'
import { useProjectApiUrl } from '@/data/config/project-endpoint-query'
import { useAsyncCheckPermissions } from '@/hooks/misc/useCheckPermissions'
import { useTrack } from '@/lib/telemetry/track'
@@ -159,18 +159,16 @@ export const ConnectSheet = () => {
PermissionAction.READ,
'service_api_keys'
)
- const { data: apiKeys } = useAPIKeysQuery({ projectRef }, { enabled: canReadAPIKeys })
- const { anonKey, publishableKey } = canReadAPIKeys
- ? getKeys(apiKeys)
- : { anonKey: null, publishableKey: null }
+ const { data: apiKeysData } = useAPIKeys({ projectRef }, { enabled: canReadAPIKeys })
const projectKeys: ProjectKeys = useMemo(() => {
+ const { anonKey, publishableKey } = apiKeysData ?? {}
return {
apiUrl: endpoint,
anonKey: anonKey?.api_key ?? null,
publishableKey: publishableKey?.api_key ?? null,
}
- }, [endpoint, anonKey?.api_key, publishableKey?.api_key])
+ }, [endpoint, apiKeysData])
const availableModes = useMemo(
() => schema.modes.filter((m) => availableModeIds.includes(m.id)),
diff --git a/apps/studio/components/interfaces/Database/Hooks/HTTPHeaders.tsx b/apps/studio/components/interfaces/Database/Hooks/HTTPHeaders.tsx
index 5638114c771..b22344bbec2 100644
--- a/apps/studio/components/interfaces/Database/Hooks/HTTPHeaders.tsx
+++ b/apps/studio/components/interfaces/Database/Hooks/HTTPHeaders.tsx
@@ -11,7 +11,7 @@ import {
FormSectionContent,
FormSectionLabel,
} from '@/components/ui/Forms/FormSection'
-import { getKeys, useAPIKeysQuery } from '@/data/api-keys/api-keys-query'
+import { useAPIKeys } from '@/data/api-keys/api-keys-query'
import { useAsyncCheckPermissions } from '@/hooks/misc/useCheckPermissions'
import { uuidv4 } from '@/lib/helpers'
@@ -23,12 +23,14 @@ export const HTTPHeaders = ({ form }: HTTPHeadersProps) => {
const { ref } = useParams()
const { can: canReadAPIKeys } = useAsyncCheckPermissions(PermissionAction.SECRETS_READ, '*')
- const { data: apiKeys } = useAPIKeysQuery(
- { projectRef: ref, reveal: true },
+ const { data: apiKeyData } = useAPIKeys(
+ {
+ projectRef: ref,
+ reveal: true,
+ },
{ enabled: canReadAPIKeys }
)
-
- const { serviceKey, secretKey } = getKeys(apiKeys)
+ const { serviceKey, secretKey } = apiKeyData ?? {}
const apiKey = secretKey?.api_key ?? serviceKey?.api_key ?? '[YOUR API KEY]'
const functionType = useWatch({ control: form.control, name: 'function_type' })
diff --git a/apps/studio/components/interfaces/Database/Replication/DestinationPanel/DestinationForm/DestinationPanelFields.tsx b/apps/studio/components/interfaces/Database/Replication/DestinationPanel/DestinationForm/DestinationPanelFields.tsx
index 3c4bf58612a..55e5f478d8c 100644
--- a/apps/studio/components/interfaces/Database/Replication/DestinationPanel/DestinationForm/DestinationPanelFields.tsx
+++ b/apps/studio/components/interfaces/Database/Replication/DestinationPanel/DestinationForm/DestinationPanelFields.tsx
@@ -24,7 +24,7 @@ import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout'
import { CREATE_NEW_KEY, CREATE_NEW_NAMESPACE } from './DestinationForm.constants'
import type { DestinationPanelSchemaType } from './DestinationForm.schema'
import { InlineLink } from '@/components/ui/InlineLink'
-import { getKeys, useAPIKeysQuery } from '@/data/api-keys/api-keys-query'
+import { useAPIKeys } from '@/data/api-keys/api-keys-query'
import { useAnalyticsBucketsQuery } from '@/data/storage/analytics-buckets-query'
import { useIcebergNamespacesQuery } from '@/data/storage/iceberg-namespaces-query'
import { useStorageCredentialsQuery } from '@/data/storage/s3-access-key-query'
@@ -370,11 +370,11 @@ export const AnalyticsBucketFields = ({
const { ref: projectRef } = useParams()
const { can: canReadAPIKeys } = useAsyncCheckPermissions(PermissionAction.SECRETS_READ, '*')
- const { data: apiKeys } = useAPIKeysQuery(
+ const { data: apiKeysData } = useAPIKeys(
{ projectRef, reveal: true },
{ enabled: canReadAPIKeys }
)
- const { serviceKey } = getKeys(apiKeys)
+ const { serviceKey } = apiKeysData ?? {}
const serviceApiKey = serviceKey?.api_key ?? ''
const {
diff --git a/apps/studio/components/interfaces/Database/Replication/DestinationPanel/DestinationForm/index.tsx b/apps/studio/components/interfaces/Database/Replication/DestinationPanel/DestinationForm/index.tsx
index 7d2c9d476e7..23b74ff3d7a 100644
--- a/apps/studio/components/interfaces/Database/Replication/DestinationPanel/DestinationForm/index.tsx
+++ b/apps/studio/components/interfaces/Database/Replication/DestinationPanel/DestinationForm/index.tsx
@@ -31,7 +31,7 @@ import { PublicationSelection } from './PublicationSelection'
import { ReplicationDisclaimerDialog } from './ReplicationDisclaimerDialog'
import { ValidationFailuresSection } from './ValidationFailuresSection'
import { CreateAnalyticsBucketSheet } from '@/components/interfaces/Storage/AnalyticsBuckets/CreateAnalyticsBucketSheet'
-import { getKeys, useAPIKeysQuery } from '@/data/api-keys/api-keys-query'
+import { useAPIKeys } from '@/data/api-keys/api-keys-query'
import { useProjectSettingsV2Query } from '@/data/config/project-settings-v2-query'
import {
BatchConfig,
@@ -149,11 +149,12 @@ export const DestinationForm = ({
pipelineId: existingDestination?.pipelineId,
})
- const { data: apiKeys } = useAPIKeysQuery(
+ const { data: apiKeysData } = useAPIKeys(
{ projectRef, reveal: true },
{ enabled: canReadAPIKeys }
)
- const { serviceKey } = getKeys(apiKeys)
+ const { serviceKey } = apiKeysData ?? {}
+
const catalogToken = serviceKey?.api_key ?? ''
const { data: projectSettings } = useProjectSettingsV2Query({ projectRef })
diff --git a/apps/studio/components/interfaces/Docs/Authentication.tsx b/apps/studio/components/interfaces/Docs/Authentication.tsx
index d38f2440fe8..d0937e1c7e3 100644
--- a/apps/studio/components/interfaces/Docs/Authentication.tsx
+++ b/apps/studio/components/interfaces/Docs/Authentication.tsx
@@ -5,7 +5,7 @@ import CodeSnippet from './CodeSnippet'
import { DocSection } from './DocSection'
import Snippets from './Snippets'
import { InlineLink } from '@/components/ui/InlineLink'
-import { getKeys, useAPIKeysQuery } from '@/data/api-keys/api-keys-query'
+import { useAPIKeys } from '@/data/api-keys/api-keys-query'
import { useProjectSettingsV2Query } from '@/data/config/project-settings-v2-query'
import { useAsyncCheckPermissions } from '@/hooks/misc/useCheckPermissions'
@@ -17,10 +17,11 @@ interface AuthenticationProps {
const Authentication = ({ selectedLang, showApiKey }: AuthenticationProps) => {
const { ref: projectRef } = useParams()
const { can: canReadAPIKeys } = useAsyncCheckPermissions(PermissionAction.SECRETS_READ, '*')
- const { data: apiKeys } = useAPIKeysQuery({ projectRef }, { enabled: canReadAPIKeys })
+ const { data: apiKeyData } = useAPIKeys({ projectRef }, { enabled: canReadAPIKeys })
+ const { anonKey, serviceKey } = apiKeyData ?? {}
+
const { data: settings } = useProjectSettingsV2Query({ projectRef })
- const { anonKey, serviceKey } = getKeys(apiKeys)
const protocol = settings?.app_config?.protocol ?? 'https'
const hostEndpoint = settings?.app_config?.endpoint
const endpoint = `${protocol}://${hostEndpoint ?? ''}`
diff --git a/apps/studio/components/interfaces/Functions/EdgeFunctionDetails/EdgeFunctionDetails.tsx b/apps/studio/components/interfaces/Functions/EdgeFunctionDetails/EdgeFunctionDetails.tsx
index a0feafae502..1d584d547fb 100644
--- a/apps/studio/components/interfaces/Functions/EdgeFunctionDetails/EdgeFunctionDetails.tsx
+++ b/apps/studio/components/interfaces/Functions/EdgeFunctionDetails/EdgeFunctionDetails.tsx
@@ -42,7 +42,7 @@ import z from 'zod'
import CommandRender from '../CommandRender'
import { INVOCATION_TABS } from './EdgeFunctionDetails.constants'
import { generateCLICommands } from './EdgeFunctionDetails.utils'
-import { getKeys, useAPIKeysQuery } from '@/data/api-keys/api-keys-query'
+import { useAPIKeys } from '@/data/api-keys/api-keys-query'
import { useProjectApiUrl } from '@/data/config/project-endpoint-query'
import { useEdgeFunctionQuery } from '@/data/edge-functions/edge-function-query'
import { useEdgeFunctionDeleteMutation } from '@/data/edge-functions/edge-functions-delete-mutation'
@@ -79,7 +79,8 @@ export const EdgeFunctionDetails = () => {
const canUpdateEdgeFunction = IS_PLATFORM && canUpdateEdgeFunctionPermission
const { can: canReadAPIKeys } = useAsyncCheckPermissions(PermissionAction.SECRETS_READ, '*')
- const { data: apiKeys } = useAPIKeysQuery({ projectRef }, { enabled: canReadAPIKeys })
+ const { data: apiKeyData } = useAPIKeys({ projectRef }, { enabled: canReadAPIKeys })
+ const { anonKey, publishableKey } = apiKeyData ?? {}
const { data: selectedFunction } = useEdgeFunctionQuery({ projectRef, slug: functionSlug })
@@ -99,7 +100,6 @@ export const EdgeFunctionDetails = () => {
defaultValues: { name: '', verify_jwt: false },
})
- const { anonKey, publishableKey } = getKeys(apiKeys)
const apiKey = publishableKey?.api_key ?? anonKey?.api_key ?? '[YOUR ANON KEY]'
const { managementCommands } = generateCLICommands({
diff --git a/apps/studio/components/interfaces/Functions/EdgeFunctionDetails/EdgeFunctionTesterSheet.tsx b/apps/studio/components/interfaces/Functions/EdgeFunctionDetails/EdgeFunctionTesterSheet.tsx
index af30abc98aa..ac14a0b89d6 100644
--- a/apps/studio/components/interfaces/Functions/EdgeFunctionDetails/EdgeFunctionTesterSheet.tsx
+++ b/apps/studio/components/interfaces/Functions/EdgeFunctionDetails/EdgeFunctionTesterSheet.tsx
@@ -39,7 +39,7 @@ import { HTTP_METHODS } from './EdgeFunctionDetails.constants'
import { ErrorWithStatus, ResponseData } from './EdgeFunctionDetails.types'
import { RoleImpersonationPopover } from '@/components/interfaces/RoleImpersonationSelector/RoleImpersonationPopover'
import { ShortcutTooltip } from '@/components/ui/ShortcutTooltip'
-import { getKeys, useAPIKeysQuery } from '@/data/api-keys/api-keys-query'
+import { useAPIKeys } from '@/data/api-keys/api-keys-query'
import { useSessionAccessTokenQuery } from '@/data/auth/session-access-token-query'
import { useProjectPostgrestConfigQuery } from '@/data/config/project-postgrest-config-query'
import { useProjectSettingsV2Query } from '@/data/config/project-settings-v2-query'
@@ -102,11 +102,11 @@ const EdgeFunctionTesterSheetContent = ({ visible, onClose }: EdgeFunctionTester
const [error, setError] = useState(null)
const { can: canReadAPIKeys } = useAsyncCheckPermissions(PermissionAction.SECRETS_READ, '*')
- const { data: apiKeys } = useAPIKeysQuery({ projectRef }, { enabled: canReadAPIKeys })
+ const { data: apiKeysData } = useAPIKeys({ projectRef }, { enabled: canReadAPIKeys })
+ const { serviceKey } = apiKeysData ?? {}
const { data: config } = useProjectPostgrestConfigQuery({ projectRef })
const { data: settings } = useProjectSettingsV2Query({ projectRef })
const { data: accessToken } = useSessionAccessTokenQuery({ enabled: IS_PLATFORM })
- const { serviceKey } = getKeys(apiKeys)
const track = useTrack()
const { mutate: testEdgeFunction, isPending } = useEdgeFunctionTestMutation({
diff --git a/apps/studio/components/interfaces/Functions/TerminalInstructions.tsx b/apps/studio/components/interfaces/Functions/TerminalInstructions.tsx
index fe8e76381cc..9185e6518f4 100644
--- a/apps/studio/components/interfaces/Functions/TerminalInstructions.tsx
+++ b/apps/studio/components/interfaces/Functions/TerminalInstructions.tsx
@@ -9,7 +9,7 @@ import type { Commands } from './Functions.types'
import CommandRender from '@/components/interfaces/Functions/CommandRender'
import { DocsButton } from '@/components/ui/DocsButton'
import { useAccessTokensQuery } from '@/data/access-tokens/access-tokens-query'
-import { getKeys, useAPIKeysQuery } from '@/data/api-keys/api-keys-query'
+import { useAPIKeys } from '@/data/api-keys/api-keys-query'
import { useProjectApiUrl } from '@/data/config/project-endpoint-query'
import { useAsyncCheckPermissions } from '@/hooks/misc/useCheckPermissions'
import { DOCS_URL } from '@/lib/constants'
@@ -29,12 +29,12 @@ export const TerminalInstructions = forwardRef<
const { data: tokens } = useAccessTokensQuery()
const { can: canReadAPIKeys } = useAsyncCheckPermissions(PermissionAction.SECRETS_READ, '*')
- const { data: apiKeys } = useAPIKeysQuery({ projectRef }, { enabled: canReadAPIKeys })
+ const { data: apiKeyData } = useAPIKeys({ projectRef }, { enabled: canReadAPIKeys })
+ const { anonKey, publishableKey } = apiKeyData ?? {}
const { data: endpoint } = useProjectApiUrl({ projectRef })
const functionsEndpoint = `${endpoint}/functions/v1`
- const { anonKey, publishableKey } = getKeys(apiKeys)
const apiKey = publishableKey?.api_key ?? anonKey?.api_key ?? '[YOUR ANON KEY]'
// get the .co or .net TLD from the restUrl
diff --git a/apps/studio/components/interfaces/Home/NewProjectPanel/APIKeys.tsx b/apps/studio/components/interfaces/Home/NewProjectPanel/APIKeys.tsx
index 237dc813e83..a023a338390 100644
--- a/apps/studio/components/interfaces/Home/NewProjectPanel/APIKeys.tsx
+++ b/apps/studio/components/interfaces/Home/NewProjectPanel/APIKeys.tsx
@@ -11,7 +11,7 @@ import { ConnectionIcon } from '@/components/interfaces/Connect/ConnectionIcon'
import { ConnectButton } from '@/components/interfaces/ConnectButton/ConnectButton'
import { AlertError } from '@/components/ui/AlertError'
import { InlineLink } from '@/components/ui/InlineLink'
-import { getKeys, useAPIKeysQuery } from '@/data/api-keys/api-keys-query'
+import { useAPIKeys } from '@/data/api-keys/api-keys-query'
import { useLegacyAPIKeysStatusQuery } from '@/data/api-keys/legacy-api-keys-status-query'
import { useJwtSecretUpdatingStatusQuery } from '@/data/config/jwt-secret-updating-status-query'
import { useProjectSettingsV2Query } from '@/data/config/project-settings-v2-query'
@@ -32,12 +32,12 @@ export const APIKeys = () => {
useLegacyAPIKeysStatusQuery({ projectRef }, { enabled: canReadAPIKeys })
const {
- data: apiKeys,
+ data: apiKeysData,
error: errorAPIKeys,
isError: isErrorAPIKeys,
isPending: isLoadingAPIKeys,
- } = useAPIKeysQuery({ projectRef }, { enabled: canReadAPIKeys })
- const { anonKey, serviceKey, publishableKey, secretKey } = getKeys(apiKeys)
+ } = useAPIKeys({ projectRef }, { enabled: canReadAPIKeys })
+ const { anonKey, serviceKey, publishableKey, secretKey } = apiKeysData ?? {}
const hasNewAPIKeys = !!publishableKey && !!secretKey
const isLegacyKeysEnabled = legacyAPIKeysStatusData?.enabled ?? false
diff --git a/apps/studio/components/interfaces/Integrations/CronJobs/HttpHeaderFieldsSection.tsx b/apps/studio/components/interfaces/Integrations/CronJobs/HttpHeaderFieldsSection.tsx
index c8ca4054481..5212f876416 100644
--- a/apps/studio/components/interfaces/Integrations/CronJobs/HttpHeaderFieldsSection.tsx
+++ b/apps/studio/components/interfaces/Integrations/CronJobs/HttpHeaderFieldsSection.tsx
@@ -6,7 +6,7 @@ import { KeyValueFieldArray } from 'ui-patterns/form/KeyValueFieldArray/KeyValue
import { CreateCronJobForm } from './CreateCronJobSheet/CreateCronJobSheet.constants'
import { buildEdgeFunctionHeaderAddActions } from '@/components/interfaces/Functions/httpHeaderAddActions'
-import { getKeys, useAPIKeysQuery } from '@/data/api-keys/api-keys-query'
+import { useAPIKeys } from '@/data/api-keys/api-keys-query'
import { useAsyncCheckPermissions } from '@/hooks/misc/useCheckPermissions'
interface HTTPHeaderFieldsSectionProps {
@@ -18,12 +18,12 @@ export const HTTPHeaderFieldsSection = ({ variant }: HTTPHeaderFieldsSectionProp
const { ref } = useParams()
const { can: canReadAPIKeys } = useAsyncCheckPermissions(PermissionAction.SECRETS_READ, '*')
- const { data: apiKeys } = useAPIKeysQuery(
+ const { data: apiKeysData } = useAPIKeys(
{ projectRef: ref, reveal: true },
{ enabled: canReadAPIKeys }
)
+ const { serviceKey, secretKey } = apiKeysData ?? {}
- const { serviceKey, secretKey } = getKeys(apiKeys)
const apiKey = secretKey?.api_key ?? serviceKey?.api_key ?? '[YOUR API KEY]'
const addActions =
variant === 'edge_function'
diff --git a/apps/studio/components/interfaces/ProjectAPIDocs/Content/Introduction.tsx b/apps/studio/components/interfaces/ProjectAPIDocs/Content/Introduction.tsx
index 4594cee9971..a29d0fdfe3e 100644
--- a/apps/studio/components/interfaces/ProjectAPIDocs/Content/Introduction.tsx
+++ b/apps/studio/components/interfaces/ProjectAPIDocs/Content/Introduction.tsx
@@ -9,7 +9,7 @@ import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout'
import ContentSnippet from '../ContentSnippet'
import { DOCS_CONTENT } from '../ProjectAPIDocs.constants'
import type { ContentProps } from './Content.types'
-import { getKeys, useAPIKeysQuery } from '@/data/api-keys/api-keys-query'
+import { useAPIKeys } from '@/data/api-keys/api-keys-query'
import { useProjectSettingsV2Query } from '@/data/config/project-settings-v2-query'
import { useAsyncCheckPermissions } from '@/hooks/misc/useCheckPermissions'
import { useTrack } from '@/lib/telemetry/track'
@@ -17,8 +17,10 @@ import { useTrack } from '@/lib/telemetry/track'
export const Introduction = ({ showKeys, language, apikey, endpoint }: ContentProps) => {
const { ref } = useParams()
const { can: canReadAPIKeys } = useAsyncCheckPermissions(PermissionAction.SECRETS_READ, '*')
- const { data: apiKeys } = useAPIKeysQuery({ projectRef: ref }, { enabled: canReadAPIKeys })
+ const { data: apiKeysData } = useAPIKeys({ projectRef: ref }, { enabled: canReadAPIKeys })
useProjectSettingsV2Query({ projectRef: ref })
+ const { anonKey, serviceKey } = apiKeysData ?? {}
+
const track = useTrack()
const [copied, setCopied] = useState<'anon' | 'service'>()
@@ -27,7 +29,6 @@ export const Introduction = ({ showKeys, language, apikey, endpoint }: ContentPr
if (copied !== undefined) setTimeout(() => setCopied(undefined), 2000)
}, [copied])
- const { anonKey, serviceKey } = getKeys(apiKeys)
const anonApiKey = anonKey?.api_key
const serviceApiKey = serviceKey?.api_key ?? 'SUPABASE_CLIENT_SERVICE_KEY'
diff --git a/apps/studio/components/interfaces/ProjectAPIDocs/ProjectAPIDocs.tsx b/apps/studio/components/interfaces/ProjectAPIDocs/ProjectAPIDocs.tsx
index 2e86d2b6937..b5236ff29f4 100644
--- a/apps/studio/components/interfaces/ProjectAPIDocs/ProjectAPIDocs.tsx
+++ b/apps/studio/components/interfaces/ProjectAPIDocs/ProjectAPIDocs.tsx
@@ -17,7 +17,7 @@ import { UserManagement } from './Content/UserManagement'
import { FirstLevelNav } from './FirstLevelNav'
import LanguageSelector from './LanguageSelector'
import { SecondLevelNav } from './SecondLevelNav'
-import { getKeys, useAPIKeysQuery } from '@/data/api-keys/api-keys-query'
+import { useAPIKeys } from '@/data/api-keys/api-keys-query'
import { useProjectApiUrl } from '@/data/config/project-endpoint-query'
import { useAsyncCheckPermissions } from '@/hooks/misc/useCheckPermissions'
import { useAppStateSnapshot } from '@/state/app-state'
@@ -45,17 +45,17 @@ export const ProjectAPIDocs = () => {
const language = snap.docsLanguage
const { can: canReadAPIKeys } = useAsyncCheckPermissions(PermissionAction.SECRETS_READ, '*')
- const { data: apiKeys } = useAPIKeysQuery(
+ const { data: apiKeysData } = useAPIKeys(
{ projectRef: ref },
{ enabled: snap.showProjectApiDocs && canReadAPIKeys }
)
+ const { anonKey } = apiKeysData ?? {}
const { data: endpoint } = useProjectApiUrl(
{ projectRef: ref },
{ enabled: snap.showProjectApiDocs }
)
- const { anonKey } = getKeys(apiKeys)
const apikey = showKeys
? (anonKey?.api_key ?? 'SUPABASE_CLIENT_ANON_KEY')
: 'SUPABASE_CLIENT_ANON_KEY'
diff --git a/apps/studio/components/interfaces/ProjectHome/ProjectConnectionPopover.tsx b/apps/studio/components/interfaces/ProjectHome/ProjectConnectionPopover.tsx
index b126619052b..c9911399469 100644
--- a/apps/studio/components/interfaces/ProjectHome/ProjectConnectionPopover.tsx
+++ b/apps/studio/components/interfaces/ProjectHome/ProjectConnectionPopover.tsx
@@ -15,7 +15,7 @@ import {
import { ShimmeringLoader } from 'ui-patterns'
import { getConnectionStrings } from '@/components/interfaces/Connect/DatabaseSettings.utils'
-import { getKeys, useAPIKeysQuery } from '@/data/api-keys/api-keys-query'
+import { useAPIKeys } from '@/data/api-keys/api-keys-query'
import { useProjectApiUrl } from '@/data/config/project-endpoint-query'
import { useReadReplicasQuery } from '@/data/read-replicas/replicas-query'
import { useAsyncCheckPermissions } from '@/hooks/misc/useCheckPermissions'
@@ -47,11 +47,11 @@ export const ProjectConnectionPopover = ({ projectRef }: ProjectConnectionPopove
const { data: projectUrl, isPending: isLoadingApiUrl } = useProjectApiUrl({ projectRef })
- const { data: apiKeys, isLoading: isLoadingKeys } = useAPIKeysQuery(
+ const { data, isLoading: isLoadingKeys } = useAPIKeys(
{ projectRef },
{ enabled: open && canReadAPIKeys }
)
- const { publishableKey } = canReadAPIKeys ? getKeys(apiKeys) : { publishableKey: null }
+ const { publishableKey } = data ?? {}
const { data: databases, isLoading: isLoadingDatabases } = useReadReplicasQuery(
{ projectRef },
diff --git a/apps/studio/components/interfaces/Realtime/Inspector/RealtimeTokensPopover.tsx b/apps/studio/components/interfaces/Realtime/Inspector/RealtimeTokensPopover.tsx
index e0b304f71f6..537edafb374 100644
--- a/apps/studio/components/interfaces/Realtime/Inspector/RealtimeTokensPopover.tsx
+++ b/apps/studio/components/interfaces/Realtime/Inspector/RealtimeTokensPopover.tsx
@@ -4,7 +4,7 @@ import { toast } from 'sonner'
import { RealtimeConfig } from './useRealtimeMessages'
import { RoleImpersonationPopover } from '@/components/interfaces/RoleImpersonationSelector/RoleImpersonationPopover'
-import { getKeys, useAPIKeysQuery } from '@/data/api-keys/api-keys-query'
+import { useAPIKeys } from '@/data/api-keys/api-keys-query'
import { getTemporaryAPIKey } from '@/data/api-keys/temp-api-keys-query'
import { useProjectPostgrestConfigQuery } from '@/data/config/project-postgrest-config-query'
import { useAsyncCheckPermissions } from '@/hooks/misc/useCheckPermissions'
@@ -22,15 +22,13 @@ export const RealtimeTokensPopover = ({ config, onChangeConfig }: RealtimeTokens
const snap = useRoleImpersonationStateSnapshot()
const { can: canReadAPIKeys } = useAsyncCheckPermissions(PermissionAction.SECRETS_READ, '*')
- const { data: apiKeys } = useAPIKeysQuery(
+ const { data: apiKeysData } = useAPIKeys(
{
projectRef: config.projectRef,
reveal: true,
},
{ enabled: canReadAPIKeys }
)
- const { anonKey, publishableKey } = getKeys(apiKeys)
-
const { data: postgrestConfig } = useProjectPostgrestConfigQuery(
{ projectRef: config.projectRef },
{ enabled: IS_PLATFORM }
@@ -55,6 +53,7 @@ export const RealtimeTokensPopover = ({ config, onChangeConfig }: RealtimeTokens
}, [snap.role])
useEffect(() => {
+ const { anonKey, publishableKey } = apiKeysData ?? {}
const triggerUpdateTokenBearer = async () => {
let token: string | undefined
let bearer: string | null = null
@@ -84,7 +83,7 @@ export const RealtimeTokensPopover = ({ config, onChangeConfig }: RealtimeTokens
triggerUpdateTokenBearer()
// eslint-disable-next-line react-hooks/exhaustive-deps
- }, [snap.role, anonKey])
+ }, [snap.role, apiKeysData])
return
}
diff --git a/apps/studio/components/interfaces/Storage/VectorBuckets/VectorBucketDetails/VectorBucketTableExamplesSheet.tsx b/apps/studio/components/interfaces/Storage/VectorBuckets/VectorBucketDetails/VectorBucketTableExamplesSheet.tsx
index 7a96ed77d3b..50189738df3 100644
--- a/apps/studio/components/interfaces/Storage/VectorBuckets/VectorBucketDetails/VectorBucketTableExamplesSheet.tsx
+++ b/apps/studio/components/interfaces/Storage/VectorBuckets/VectorBucketDetails/VectorBucketTableExamplesSheet.tsx
@@ -28,7 +28,7 @@ import { CodeBlock } from 'ui-patterns/CodeBlock'
import { useS3VectorsWrapperExtension } from '../useS3VectorsWrapper'
import { useS3VectorsWrapperInstance } from '../useS3VectorsWrapperInstance'
import { DocsButton } from '@/components/ui/DocsButton'
-import { getKeys, useAPIKeysQuery } from '@/data/api-keys/api-keys-query'
+import { useAPIKeys } from '@/data/api-keys/api-keys-query'
import { VectorBucketIndex } from '@/data/storage/vector-buckets-indexes-query'
import { useAsyncCheckPermissions } from '@/hooks/misc/useCheckPermissions'
import { DOCS_URL } from '@/lib/constants'
@@ -143,8 +143,8 @@ function VectorBucketIndexExamples({
PermissionAction.READ,
'service_api_keys'
)
- const { data: apiKeys } = useAPIKeysQuery({ projectRef }, { enabled: canReadAPIKeys })
- const { secretKey } = canReadAPIKeys ? getKeys(apiKeys) : { secretKey: null }
+ const { data: apiKeysData } = useAPIKeys({ projectRef }, { enabled: canReadAPIKeys })
+ const { secretKey } = apiKeysData ?? {}
const { data: wrapperInstance } = useS3VectorsWrapperInstance({ bucketId })
const foreignTable = wrapperInstance?.tables?.find((x) => x.name === indexName)
@@ -177,7 +177,7 @@ values
const jsCode = `import { createClient } from '@supabase/supabase-js'
-// Adding vector data requires a secret or service role key
+// Adding vector data requires a secret or service role key
// This code SHOULD NOT be run on the client side as you will be vulnerable to a data leak
const client = createClient(
process.env.SUPABASE_URL,
diff --git a/apps/studio/data/api-keys/api-keys-query.ts b/apps/studio/data/api-keys/api-keys-query.ts
index be0f873b947..a9f87625e38 100644
--- a/apps/studio/data/api-keys/api-keys-query.ts
+++ b/apps/studio/data/api-keys/api-keys-query.ts
@@ -1,4 +1,5 @@
import { useQuery } from '@tanstack/react-query'
+import { useCallback } from 'react'
import { apiKeysKeys } from './keys'
import { get, handleError } from '@/data/fetchers'
@@ -78,7 +79,18 @@ export const useAPIKeysQuery = (
})
}
+const EmptyKey = {
+ anonKey: undefined,
+ serviceKey: undefined,
+ publishableKey: undefined,
+ secretKey: undefined,
+ allSecretKeys: [],
+}
+
export const getKeys = (apiKeys: APIKey[] = []) => {
+ if (!Array.isArray(apiKeys)) {
+ return EmptyKey
+ }
const anonKey = apiKeys.find((x) => x.name === 'anon')
const serviceKey = apiKeys.find((x) => x.name === 'service_role')
@@ -90,3 +102,21 @@ export const getKeys = (apiKeys: APIKey[] = []) => {
return { anonKey, serviceKey, publishableKey, secretKey, allSecretKeys }
}
+
+const PlaceholderData: APIKeysData = []
+
+export const useAPIKeys = (
+ variables: APIKeysVariables,
+ options: UseCustomQueryOptions = {}
+) => {
+ const select = useCallback(
+ (data: APIKeysData) => getKeys(options.enabled ? data : []),
+ [options.enabled]
+ )
+
+ return useAPIKeysQuery(variables, {
+ ...options,
+ placeholderData: PlaceholderData,
+ select,
+ })
+}
diff --git a/apps/studio/data/storage/iceberg-wrapper-create-mutation.ts b/apps/studio/data/storage/iceberg-wrapper-create-mutation.ts
index 3876f34ff2a..c98ad6d0217 100644
--- a/apps/studio/data/storage/iceberg-wrapper-create-mutation.ts
+++ b/apps/studio/data/storage/iceberg-wrapper-create-mutation.ts
@@ -10,7 +10,7 @@ import {
getCatalogURI,
getConnectionURL,
} from '@/components/interfaces/Storage/StorageSettings/StorageSettings.utils'
-import { getKeys, useAPIKeysQuery } from '@/data/api-keys/api-keys-query'
+import { useAPIKeys } from '@/data/api-keys/api-keys-query'
import { useProjectSettingsV2Query } from '@/data/config/project-settings-v2-query'
import { FDWCreateVariables, useFDWCreateMutation } from '@/data/fdw/fdw-create-mutation'
import { useAsyncCheckPermissions } from '@/hooks/misc/useCheckPermissions'
@@ -20,12 +20,11 @@ export const useIcebergWrapperCreateMutation = () => {
const { data: project } = useSelectedProjectQuery()
const { can: canReadAPIKeys } = useAsyncCheckPermissions(PermissionAction.SECRETS_READ, '*')
- const { data: apiKeys } = useAPIKeysQuery(
+ const { data: apiKeysData } = useAPIKeys(
{ projectRef: project?.ref, reveal: true },
{ enabled: canReadAPIKeys }
)
- const { secretKey, serviceKey } = getKeys(apiKeys)
-
+ const { secretKey, serviceKey } = apiKeysData ?? {}
const { data: settings } = useProjectSettingsV2Query({ projectRef: project?.ref })
const protocol = settings?.app_config?.protocol ?? 'https'
const endpoint = settings?.app_config?.storage_endpoint || settings?.app_config?.endpoint