Files
supabase/apps/studio/state/editor-panel-state.tsx
Charis 0433eeb5f5 feat(studio): mark sql provenance for safety (#45336)
Mark provenance of SQL via the branded types SafeSqlFragment and
UntrustedSqlFragment. Only SafeSqlFragment should be executed;
UntrustedSqlFragments require some kind of implicit user approval (show
on screen + user has to click something) before they are promoted to
SafeSqlFragment.

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Editor and RLS tester show loading states for inferred/generated SQL
and include a dedicated user SQL editor for safer edits.

* **Refactor**
* Platform-wide SQL handling tightened: snippets and AI-generated SQL
are treated as untrusted/display-only until promoted, improving safety
and consistency.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-05-04 13:08:06 -04:00

76 lines
2.0 KiB
TypeScript

import { safeSql, type DisplayableSqlFragment } from '@supabase/pg-meta'
import { proxy, snapshot, useSnapshot } from 'valtio'
type Template = {
name: string
description: string
content: string
}
export type SqlError = {
error?: string
formattedError?: string
message?: string
}
type EditorPanelState = {
value: DisplayableSqlFragment
templates: Template[]
results: Record<string, unknown>[] | undefined
error: SqlError | undefined
initialPrompt: string
onChange: ((value: DisplayableSqlFragment) => void) | undefined
activeSnippetId: string | null
pendingReset: boolean
}
const initialState: EditorPanelState = {
value: safeSql``,
templates: [],
results: undefined,
error: undefined,
initialPrompt: '',
onChange: undefined,
activeSnippetId: null,
pendingReset: false,
}
export const editorPanelState = proxy({
...initialState,
setValue(value: DisplayableSqlFragment) {
editorPanelState.value = value
editorPanelState.onChange?.(value)
editorPanelState.setResults(undefined)
editorPanelState.setError(undefined)
},
setTemplates(templates: Template[]) {
editorPanelState.templates = templates
},
setResults(results: Record<string, unknown>[] | undefined) {
editorPanelState.results = results
},
setError(error: SqlError | undefined) {
editorPanelState.error = error
},
setInitialPrompt(initialPrompt: string) {
editorPanelState.initialPrompt = initialPrompt
},
setActiveSnippetId(id: string | null) {
editorPanelState.activeSnippetId = id
},
openAsNew() {
editorPanelState.value = safeSql``
editorPanelState.results = undefined
editorPanelState.error = undefined
editorPanelState.pendingReset = true
},
reset() {
Object.assign(editorPanelState, initialState)
},
})
export const getEditorPanelStateSnapshot = () => snapshot(editorPanelState)
export const useEditorPanelStateSnapshot = (options?: Parameters<typeof useSnapshot>[1]) =>
useSnapshot(editorPanelState, options)