Files
Charisandsupabase-autofix-bot cdc2dc4e26 refactor(studio): import SQL editor store from source, delete facade + barrel (#47533)
## What

Final PR of the SQL editor state re-layering stack. Removes the
compatibility shims left in place during the migration:

- Migrates all **23** consumers of the `@/state/sql-editor-v2` facade to
import directly from `@/state/sql-editor/sql-editor-state`, where
`useSqlEditorV2StateSnapshot`, `getSqlEditorV2StateSnapshot`,
`useSnippets`, and `useSnippetFolders` actually live.
- Deletes `state/sql-editor-v2.ts` (the facade) and
`state/sql-editor/index.ts` (the barrel). Both re-exported the same
symbols; nothing imports them after the migration.

This collapses the two-layer re-export (`sql-editor-v2` → `index` →
source) into direct source imports, matching the repo convention to
avoid barrel re-export files.

## Notes

- Pure import-path migration — no behavior change. All 23 consumers
imported only value symbols that resolve to `sql-editor-state.ts`; none
imported the `StateSnippet`/`StateSnippetFolder` types via the facade.
- Symbol names keep their `V2` suffix for now — renaming
`useSqlEditorV2StateSnapshot` etc. is a separate, larger churn best done
on its own.
- 25 files: 23 one-line import changes + 2 deletions (23 insertions / 39
deletions).

## Validation

- `pnpm --filter studio typecheck` ✅ (confirms no dangling facade/barrel
imports anywhere)
- `pnpm exec vitest --run state/sql-editor/` ✅ (113 passed)
- lint ✅ (0 errors; no ratcheted-rule regressions — a path swap can't
add `any`/deps/nested-component violations, and no import-order rule is
enforced)
- grep confirms zero remaining `sql-editor-v2` references

---------

Co-authored-by: supabase-autofix-bot <noreply@supabase.com>
2026-07-02 13:15:47 -04:00

152 lines
4.0 KiB
TypeScript

import { PermissionAction } from '@supabase/shared-types/out/constants'
import { useParams } from 'common'
import { useRouter } from 'next/router'
import { useCallback, useEffect, useMemo, useState } from 'react'
import { toast } from 'sonner'
import { ContentDiff, DiffType } from './SQLEditor.types'
import {
compareAsAddition,
compareAsModification,
compareAsNewSnippet,
createSqlSnippetSkeletonV2,
} from './SQLEditor.utils'
import { useAsyncCheckPermissions } from '@/hooks/misc/useCheckPermissions'
import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject'
import { useProfile } from '@/lib/profile'
import { useSqlEditorV2StateSnapshot } from '@/state/sql-editor/sql-editor-state'
export const useNewQuery = () => {
const router = useRouter()
const { ref } = useParams()
const { profile } = useProfile()
const { data: project } = useSelectedProjectQuery()
const snapV2 = useSqlEditorV2StateSnapshot()
const { can: canCreateSQLSnippet } = useAsyncCheckPermissions(
PermissionAction.CREATE,
'user_content',
{
resource: { type: 'sql', owner_id: profile?.id },
subject: { id: profile?.id },
}
)
const newQuery = async (sql: string, name: string, shouldRedirect: boolean = true) => {
if (!ref) return console.error('Project ref is required')
if (!project) return console.error('Project is required')
if (!profile) return console.error('Profile is required')
if (!canCreateSQLSnippet) {
toast('Your queries will not be saved as you do not have sufficient permissions')
return undefined
}
try {
const snippet = createSqlSnippetSkeletonV2({
name,
sql,
owner_id: profile?.id,
project_id: project?.id,
})
snapV2.addSnippet({ projectRef: ref, snippet })
snapV2.addNeedsSaving(snippet.id)
if (shouldRedirect) {
router.push(`/project/${ref}/sql/${snippet.id}`)
return undefined
} else {
return snippet.id
}
} catch (error: any) {
toast.error(`Failed to create new query: ${error.message}`)
return undefined
}
}
return { newQuery }
}
export function useSqlEditorDiff() {
const [sourceSqlDiff, setSourceSqlDiff] = useState<ContentDiff>()
const [selectedDiffType, setSelectedDiffType] = useState<DiffType>()
const [isAcceptDiffLoading, setIsAcceptDiffLoading] = useState(false)
const isDiffOpen = !!sourceSqlDiff
const defaultSqlDiff = useMemo(() => {
if (!sourceSqlDiff) {
return { original: '', modified: '' }
}
switch (selectedDiffType) {
case DiffType.Modification:
return compareAsModification(sourceSqlDiff)
case DiffType.Addition:
return compareAsAddition(sourceSqlDiff)
case DiffType.NewSnippet:
return compareAsNewSnippet(sourceSqlDiff)
default:
return { original: '', modified: '' }
}
}, [selectedDiffType, sourceSqlDiff])
const closeDiff = useCallback(() => {
setSourceSqlDiff(undefined)
setSelectedDiffType(undefined)
}, [])
return {
sourceSqlDiff,
setSourceSqlDiff,
selectedDiffType,
setSelectedDiffType,
isAcceptDiffLoading,
setIsAcceptDiffLoading,
isDiffOpen,
defaultSqlDiff,
closeDiff,
}
}
interface PromptState {
isOpen: boolean
selection: string
beforeSelection: string
afterSelection: string
startLineNumber: number
endLineNumber: number
}
const initialPromptState: PromptState = {
isOpen: false,
selection: '',
beforeSelection: '',
afterSelection: '',
startLineNumber: 0,
endLineNumber: 0,
}
export function useSqlEditorPrompt() {
const [promptState, setPromptState] = useState<PromptState>(initialPromptState)
const [promptInput, setPromptInput] = useState('')
useEffect(() => {
if (!promptState.isOpen) {
setPromptInput('')
}
}, [promptState.isOpen])
const resetPrompt = () => {
setPromptState(initialPromptState)
setPromptInput('')
}
return {
promptState,
setPromptState,
promptInput,
setPromptInput,
resetPrompt,
}
}