{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "sql-runner",
  "title": "SQLRunner",
  "description": "A guarded SQL editor with run and cancel controls, inline Postgres errors, and a typed results grid.",
  "dependencies": [
    "@hugeicons/react",
    "@hugeicons/core-free-icons",
    "codemirror",
    "@codemirror/lang-sql",
    "@codemirror/language",
    "@codemirror/state",
    "@codemirror/view",
    "@lezer/highlight",
    "@codemirror/lint",
    "@codemirror/search"
  ],
  "registryDependencies": [
    "utils",
    "https://ui.neon.com/r/button.json",
    "https://ui.neon.com/r/neon-tokens.json"
  ],
  "files": [
    {
      "path": "src/components/sql-runner/sql-runner.tsx",
      "content": "\"use client\";\n\nimport { PostgreSQL, sql } from \"@codemirror/lang-sql\";\nimport {\n  bracketMatching,\n  HighlightStyle,\n  syntaxHighlighting,\n} from \"@codemirror/language\";\nimport { lintGutter, setDiagnostics } from \"@codemirror/lint\";\nimport { highlightSelectionMatches, searchKeymap } from \"@codemirror/search\";\nimport { Annotation, Compartment, Prec } from \"@codemirror/state\";\nimport {\n  EditorView,\n  highlightActiveLineGutter,\n  keymap,\n  lineNumbers,\n  placeholder,\n} from \"@codemirror/view\";\nimport {\n  Alert02Icon,\n  CheckmarkCircle02Icon,\n  Clock01Icon,\n  Database01Icon,\n  Loading03Icon,\n  PlayIcon,\n  Shield01Icon,\n  StopIcon,\n} from \"@hugeicons/core-free-icons\";\nimport { HugeiconsIcon } from \"@hugeicons/react\";\nimport { tags } from \"@lezer/highlight\";\nimport { minimalSetup } from \"codemirror\";\nimport { useEffect, useRef, useState } from \"react\";\nimport type { ComponentProps } from \"react\";\n\nimport { Button } from \"@/components/ui/button\";\nimport { cn } from \"@/lib/utils\";\n\nexport type SQLRunnerMode = \"read\" | \"read-write\";\n\nexport interface SQLField {\n  name: string;\n  /** Database type label, e.g. \"uuid\" or \"timestamptz\". */\n  type?: string;\n}\n\nexport type SQLRow = Record<string, unknown>;\n\nexport interface SQLResult {\n  rows: SQLRow[];\n  fields?: SQLField[];\n  /** Total rows returned or affected. Defaults to rows.length. */\n  rowCount?: number;\n  /** Query duration. Measured by SQLRunner when omitted. */\n  durationMs?: number;\n  /** Postgres command tag, e.g. \"SELECT\", \"UPDATE\", or \"CREATE TABLE\". */\n  command?: string;\n}\n\nexport interface SQLRunnerError {\n  message: string;\n  code?: string;\n  detail?: string;\n  hint?: string;\n  line?: number;\n  column?: number;\n}\n\nexport interface SQLExecutionContext {\n  mode: SQLRunnerMode;\n  signal: AbortSignal;\n}\n\nconst WRITE_KEYWORDS = new Set([\n  \"ALTER\",\n  \"CALL\",\n  \"CLUSTER\",\n  \"COMMENT\",\n  \"COPY\",\n  \"CREATE\",\n  \"DELETE\",\n  \"DO\",\n  \"DROP\",\n  \"GRANT\",\n  \"INSERT\",\n  \"LOCK\",\n  \"MERGE\",\n  \"REFRESH\",\n  \"REINDEX\",\n  \"REVOKE\",\n  \"SET\",\n  \"TRUNCATE\",\n  \"UPDATE\",\n  \"VACUUM\",\n]);\n\nconst stripSQLNoise = (query: string) =>\n  query\n    .replaceAll(/--.*$/gmu, \" \")\n    .replaceAll(/\\/\\*[\\s\\S]*?\\*\\//gu, \" \")\n    .replaceAll(/'(?:''|[^'])*'/gu, \"''\")\n    .replaceAll(/\"(?:\"\"|[^\"])*\"/gu, '\"\"');\n\n/** Conservative client-side guard. The database remains the authority. */\nexport const isWriteQuery = (query: string) => {\n  const tokens =\n    stripSQLNoise(query)\n      .toUpperCase()\n      .match(/[A-Z_]+/gu) ?? [];\n  return tokens.some((token) => WRITE_KEYWORDS.has(token));\n};\n\nconst toRunnerError = (error: unknown): SQLRunnerError => {\n  if (error instanceof DOMException && error.name === \"AbortError\") {\n    return { message: \"Query cancelled.\" };\n  }\n  if (error instanceof Error) {\n    const source = error as Error & Partial<SQLRunnerError>;\n    return {\n      code: source.code,\n      column: source.column,\n      detail: source.detail,\n      hint: source.hint,\n      line: source.line,\n      message: source.message,\n    };\n  }\n  return { message: \"The query failed. Check the SQL and try again.\" };\n};\n\nconst formatDuration = (durationMs: number) => {\n  if (durationMs < 1) {\n    return \"<1 ms\";\n  }\n  if (durationMs < 1000) {\n    return `${Math.round(durationMs)} ms`;\n  }\n  return `${(durationMs / 1000).toFixed(2)} s`;\n};\n\nconst formatCell = (value: unknown) => {\n  if (value === null) {\n    return \"NULL\";\n  }\n  if (value === undefined) {\n    return \"\";\n  }\n  if (value instanceof Date) {\n    return value.toISOString();\n  }\n  if (typeof value === \"object\") {\n    try {\n      return JSON.stringify(value);\n    } catch {\n      return String(value);\n    }\n  }\n  return String(value);\n};\n\nconst getColumns = (result: SQLResult): SQLField[] => {\n  if (result.fields && result.fields.length > 0) {\n    return result.fields;\n  }\n  const [firstRow] = result.rows;\n  return firstRow\n    ? Object.keys(firstRow).map((name): SQLField => ({ name }))\n    : [];\n};\n\nconst ModeControl = ({\n  mode,\n  onChange,\n}: {\n  mode: SQLRunnerMode;\n  onChange: (mode: SQLRunnerMode) => void;\n}) => (\n  <fieldset className=\"flex h-7 items-center rounded-md border border-border/60 bg-background p-0.5\">\n    <legend className=\"sr-only\">Query safety mode</legend>\n    <button\n      aria-pressed={mode === \"read\"}\n      className={cn(\n        \"h-6 rounded-[4px] px-2 font-medium text-[11px] outline-none transition-colors\",\n        \"hover:text-foreground focus-visible:ring-2 focus-visible:ring-ring/50\",\n        mode === \"read\"\n          ? \"bg-muted text-foreground shadow-xs\"\n          : \"text-muted-foreground\"\n      )}\n      onClick={() => onChange(\"read\")}\n      type=\"button\"\n    >\n      Read only\n    </button>\n    <button\n      aria-pressed={mode === \"read-write\"}\n      className={cn(\n        \"h-6 rounded-[4px] px-2 font-medium text-[11px] outline-none transition-colors\",\n        \"hover:text-foreground focus-visible:ring-2 focus-visible:ring-ring/50\",\n        mode === \"read-write\"\n          ? \"bg-[var(--status-scaling)]/15 text-[var(--status-scaling)] shadow-xs\"\n          : \"text-muted-foreground\"\n      )}\n      onClick={() => onChange(\"read-write\")}\n      type=\"button\"\n    >\n      Read + write\n    </button>\n  </fieldset>\n);\n\nconst EXTERNAL_UPDATE = Annotation.define<boolean>();\n\nconst editorTheme = EditorView.theme({\n  \"&\": {\n    backgroundColor: \"var(--background)\",\n    color: \"var(--foreground)\",\n    fontSize: \"13px\",\n    height: \"190px\",\n  },\n  \"&.cm-focused\": { outline: \"none\" },\n  \"&.cm-focused .cm-cursor\": { borderLeftColor: \"var(--primary)\" },\n  \"&.cm-focused .cm-selectionBackground, .cm-selectionBackground\": {\n    backgroundColor:\n      \"color-mix(in oklch, var(--primary) 16%, transparent) !important\",\n  },\n  \".cm-activeLineGutter\": {\n    backgroundColor: \"color-mix(in oklch, var(--muted) 60%, transparent)\",\n    color: \"var(--foreground)\",\n  },\n  \".cm-content\": {\n    caretColor: \"var(--primary)\",\n    padding: \"12px 0\",\n  },\n  \".cm-content ::selection\": {\n    backgroundColor: \"color-mix(in oklch, var(--primary) 16%, transparent)\",\n    color: \"var(--foreground)\",\n  },\n  \".cm-editor\": { minWidth: \"0\" },\n  \".cm-gutters\": {\n    backgroundColor: \"color-mix(in oklch, var(--muted) 22%, transparent)\",\n    borderRight:\n      \"1px solid color-mix(in oklch, var(--border) 55%, transparent)\",\n    color: \"color-mix(in oklch, var(--muted-foreground) 55%, transparent)\",\n  },\n  \".cm-line\": { padding: \"0 12px\" },\n  \".cm-lineNumbers .cm-gutterElement\": {\n    minWidth: \"40px\",\n    padding: \"0 10px 0 8px\",\n  },\n  \".cm-lintRange-error\": {\n    backgroundImage: \"none\",\n    textDecoration: \"underline wavy var(--destructive)\",\n    textUnderlineOffset: \"3px\",\n  },\n  \".cm-scroller\": {\n    fontFamily: \"var(--font-mono)\",\n    lineHeight: \"20px\",\n    overscrollBehavior: \"contain\",\n  },\n  \".cm-tooltip\": {\n    backgroundColor: \"var(--popover)\",\n    border: \"1px solid var(--border)\",\n    borderRadius: \"var(--radius-md)\",\n    boxShadow: \"var(--shadow-lg)\",\n    color: \"var(--popover-foreground)\",\n    fontFamily: \"var(--font-sans)\",\n    fontSize: \"12px\",\n    overflow: \"hidden\",\n  },\n});\n\nconst sqlHighlighting = syntaxHighlighting(\n  HighlightStyle.define([\n    { color: \"var(--primary)\", fontWeight: \"600\", tag: tags.keyword },\n    { color: \"var(--status-scaling)\", tag: tags.string },\n    {\n      color: \"var(--muted-foreground)\",\n      fontStyle: \"italic\",\n      tag: tags.comment,\n    },\n    { color: \"var(--foreground)\", tag: [tags.name, tags.variableName] },\n    {\n      color: \"var(--status-sleeping)\",\n      tag: [tags.number, tags.bool, tags.null],\n    },\n    { color: \"var(--muted-foreground)\", tag: tags.punctuation },\n    { color: \"var(--destructive)\", tag: tags.invalid },\n  ])\n);\n\ninterface EditorPosition {\n  column: number;\n  line: number;\n  selectedCharacters: number;\n}\n\nconst Editor = ({\n  value,\n  onChange,\n  onRun,\n  onCancel,\n  onPositionChange,\n  error,\n  running,\n  disabled,\n}: {\n  value: string;\n  onChange: (value: string) => void;\n  onRun: (selection?: string) => void;\n  onCancel: () => void;\n  onPositionChange: (position: EditorPosition, selection: string) => void;\n  error: SQLRunnerError | null;\n  running: boolean;\n  disabled: boolean;\n}) => {\n  const mountRef = useRef<HTMLDivElement>(null);\n  const viewRef = useRef<EditorView | null>(null);\n  const editable = useRef(new Compartment());\n  const onChangeRef = useRef(onChange);\n  const onRunRef = useRef(onRun);\n  const onCancelRef = useRef(onCancel);\n  const onPositionChangeRef = useRef(onPositionChange);\n  const runningRef = useRef(running);\n  const initialValueRef = useRef(value);\n  const initialDisabledRef = useRef(disabled);\n\n  useEffect(() => {\n    onChangeRef.current = onChange;\n    onRunRef.current = onRun;\n    onCancelRef.current = onCancel;\n    onPositionChangeRef.current = onPositionChange;\n    runningRef.current = running;\n  }, [onCancel, onChange, onPositionChange, onRun, running]);\n\n  useEffect(() => {\n    if (!mountRef.current) {\n      return;\n    }\n\n    const reportSelection = (view: EditorView) => {\n      const selection = view.state.selection.main;\n      const line = view.state.doc.lineAt(selection.head);\n      const selected = view.state.sliceDoc(selection.from, selection.to);\n      onPositionChangeRef.current(\n        {\n          column: selection.head - line.from + 1,\n          line: line.number,\n          selectedCharacters: selection.to - selection.from,\n        },\n        selected\n      );\n    };\n\n    const view = new EditorView({\n      doc: initialValueRef.current,\n      extensions: [\n        minimalSetup,\n        lineNumbers(),\n        highlightActiveLineGutter(),\n        bracketMatching(),\n        highlightSelectionMatches(),\n        lintGutter(),\n        keymap.of(searchKeymap),\n        sql({ dialect: PostgreSQL }),\n        editorTheme,\n        sqlHighlighting,\n        placeholder(\"select * from users limit 20;\"),\n        EditorView.contentAttributes.of({\n          \"aria-label\": \"SQL query\",\n          autocapitalize: \"off\",\n          autocomplete: \"off\",\n          spellcheck: \"false\",\n        }),\n        editable.current.of(\n          EditorView.editable.of(!initialDisabledRef.current)\n        ),\n        Prec.high(\n          keymap.of([\n            {\n              key: \"Mod-Enter\",\n              run: (currentView) => {\n                if (runningRef.current) {\n                  return true;\n                }\n                const selection = currentView.state.selection.main;\n                const selected = currentView.state.sliceDoc(\n                  selection.from,\n                  selection.to\n                );\n                onRunRef.current(selected || undefined);\n                return true;\n              },\n            },\n            {\n              key: \"Escape\",\n              run: () => {\n                if (!runningRef.current) {\n                  return false;\n                }\n                onCancelRef.current();\n                return true;\n              },\n            },\n          ])\n        ),\n        EditorView.updateListener.of((update) => {\n          if (\n            update.docChanged &&\n            !update.transactions.some((transaction) =>\n              transaction.annotation(EXTERNAL_UPDATE)\n            )\n          ) {\n            onChangeRef.current(update.state.doc.toString());\n          }\n          if (update.docChanged || update.selectionSet) {\n            reportSelection(update.view);\n          }\n        }),\n      ],\n      parent: mountRef.current,\n    });\n    viewRef.current = view;\n    reportSelection(view);\n\n    return () => {\n      view.destroy();\n      viewRef.current = null;\n    };\n  }, []);\n\n  useEffect(() => {\n    const view = viewRef.current;\n    if (!view || view.state.doc.toString() === value) {\n      return;\n    }\n    view.dispatch({\n      annotations: EXTERNAL_UPDATE.of(true),\n      changes: { from: 0, insert: value, to: view.state.doc.length },\n    });\n  }, [value]);\n\n  useEffect(() => {\n    viewRef.current?.dispatch({\n      effects: editable.current.reconfigure(EditorView.editable.of(!disabled)),\n    });\n  }, [disabled]);\n\n  useEffect(() => {\n    const view = viewRef.current;\n    if (!view) {\n      return;\n    }\n    if (!error) {\n      view.dispatch(setDiagnostics(view.state, []));\n      return;\n    }\n\n    const lineNumber = Math.max(\n      1,\n      Math.min(error.line ?? 1, view.state.doc.lines)\n    );\n    const line = view.state.doc.line(lineNumber);\n    const from = Math.min(\n      line.to,\n      line.from + Math.max(0, (error.column ?? 1) - 1)\n    );\n    const detail = [error.message, error.detail, error.hint]\n      .filter(Boolean)\n      .join(\"\\n\");\n    view.dispatch(\n      setDiagnostics(view.state, [\n        {\n          from,\n          message: detail,\n          severity: \"error\",\n          source: error.code,\n          to: Math.min(line.to, from + 1),\n        },\n      ])\n    );\n  }, [error]);\n\n  return (\n    <div className=\"overflow-hidden bg-background\">\n      <div\n        className=\"min-w-0 [&_.cm-editor.cm-focused]:ring-2 [&_.cm-editor.cm-focused]:ring-inset [&_.cm-editor.cm-focused]:ring-ring/40\"\n        ref={mountRef}\n      />\n    </div>\n  );\n};\n\nconst ErrorPanel = ({ error }: { error: SQLRunnerError }) => (\n  <div\n    aria-live=\"assertive\"\n    className=\"border-destructive/30 border-t bg-destructive/5 px-3 py-2.5\"\n    data-slot=\"sql-runner-error\"\n  >\n    <div className=\"flex gap-2\">\n      <HugeiconsIcon\n        aria-hidden=\"true\"\n        className=\"mt-0.5 size-3.5 shrink-0 text-destructive\"\n        icon={Alert02Icon}\n        strokeWidth={2}\n      />\n      <div className=\"min-w-0\">\n        <div className=\"flex flex-wrap items-baseline gap-x-2 gap-y-0.5\">\n          <p className=\"font-medium text-destructive text-xs\">\n            {error.message}\n          </p>\n          {error.code ? (\n            <code className=\"text-[10px] text-destructive/70\">\n              {error.code}\n            </code>\n          ) : null}\n          {error.line ? (\n            <span className=\"text-[10px] text-muted-foreground\">\n              Line {error.line}\n              {error.column ? `, column ${error.column}` : \"\"}\n            </span>\n          ) : null}\n        </div>\n        {error.detail ? (\n          <p className=\"mt-1 text-[11px] text-muted-foreground\">\n            {error.detail}\n          </p>\n        ) : null}\n        {error.hint ? (\n          <p className=\"mt-1 text-[11px] text-muted-foreground\">\n            Hint: {error.hint}\n          </p>\n        ) : null}\n      </div>\n    </div>\n  </div>\n);\n\nconst BlockedWrite = ({ onEnable }: { onEnable: () => void }) => (\n  <div\n    aria-live=\"polite\"\n    className=\"flex flex-wrap items-center gap-2 border-[var(--status-scaling)]/30 border-t bg-[var(--status-scaling)]/5 px-3 py-2\"\n  >\n    <HugeiconsIcon\n      aria-hidden=\"true\"\n      className=\"size-3.5 shrink-0 text-[var(--status-scaling)]\"\n      icon={Shield01Icon}\n      strokeWidth={2}\n    />\n    <p className=\"min-w-48 flex-1 text-[11px] text-muted-foreground\">\n      This statement can change data. Read-only mode stopped it before\n      execution.\n    </p>\n    <Button onClick={onEnable} size=\"xs\" variant=\"outline\">\n      Enable writes\n    </Button>\n  </div>\n);\n\nconst ResultsGrid = ({ result }: { result: SQLResult }) => {\n  const columns = getColumns(result);\n  const rowCount = result.rowCount ?? result.rows.length;\n  const hasRows = result.rows.length > 0;\n\n  if (!hasRows) {\n    return (\n      <div className=\"grid min-h-32 place-items-center px-5 py-8 text-center\">\n        <div>\n          <span className=\"mx-auto mb-2 grid size-8 place-items-center rounded-full bg-primary/10 text-primary\">\n            <HugeiconsIcon\n              aria-hidden=\"true\"\n              className=\"size-4\"\n              icon={CheckmarkCircle02Icon}\n              strokeWidth={2}\n            />\n          </span>\n          <p className=\"font-medium text-xs\">\n            {result.command ? `${result.command} complete` : \"Query complete\"}\n          </p>\n          <p className=\"mt-1 text-[11px] text-muted-foreground tabular-nums\">\n            {rowCount} {rowCount === 1 ? \"row\" : \"rows\"} affected\n          </p>\n        </div>\n      </div>\n    );\n  }\n\n  return (\n    <div className=\"neon-scroll-fade max-h-80 overflow-auto\">\n      <table className=\"w-full min-w-max border-collapse text-left text-xs\">\n        <thead className=\"sticky top-0 z-10 bg-card shadow-[0_1px_0_0_var(--border)]\">\n          <tr>\n            <th className=\"w-10 bg-muted/20 px-2 py-2 text-right font-normal text-[10px] text-muted-foreground\">\n              #\n            </th>\n            {columns.map((column) => (\n              <th className=\"min-w-28 px-3 py-2 font-medium\" key={column.name}>\n                <span className=\"block font-mono text-[11px]\">\n                  {column.name}\n                </span>\n                {column.type ? (\n                  <span className=\"mt-0.5 block font-normal text-[9px] text-muted-foreground\">\n                    {column.type}\n                  </span>\n                ) : null}\n              </th>\n            ))}\n          </tr>\n        </thead>\n        <tbody>\n          {result.rows.map((row, rowIndex) => (\n            <tr\n              className=\"border-border/45 border-b last:border-b-0 hover:bg-muted/30\"\n              key={rowIndex}\n            >\n              <td className=\"bg-muted/10 px-2 py-2 text-right font-mono text-[10px] text-muted-foreground/60 tabular-nums\">\n                {rowIndex + 1}\n              </td>\n              {columns.map((column) => {\n                const value = row[column.name];\n                const formatted = formatCell(value);\n                return (\n                  <td\n                    className={cn(\n                      \"max-w-72 px-3 py-2 font-mono text-[11px] tabular-nums\",\n                      value === null\n                        ? \"text-muted-foreground/50 italic\"\n                        : \"text-foreground/85\"\n                    )}\n                    key={column.name}\n                    title={formatted}\n                  >\n                    <span className=\"block truncate\">{formatted}</span>\n                  </td>\n                );\n              })}\n            </tr>\n          ))}\n        </tbody>\n      </table>\n    </div>\n  );\n};\n\nexport type SQLRunnerProps = Omit<ComponentProps<\"div\">, \"children\"> & {\n  /** Execute SQL. Throw an Error or SQLRunnerError-shaped error on failure. */\n  onExecute: (\n    query: string,\n    context: SQLExecutionContext\n  ) => Promise<SQLResult>;\n  value?: string;\n  defaultValue?: string;\n  onValueChange?: (value: string) => void;\n  mode?: SQLRunnerMode;\n  defaultMode?: SQLRunnerMode;\n  onModeChange?: (mode: SQLRunnerMode) => void;\n  title?: string;\n  database?: string;\n  disabled?: boolean;\n};\n\n// oxlint-disable-next-line eslint/complexity -- explicit runner states stay co-located for honest precedence\nexport const SQLRunner = ({\n  onExecute,\n  value,\n  defaultValue = \"\",\n  onValueChange,\n  mode,\n  defaultMode = \"read\",\n  onModeChange,\n  title = \"SQL editor\",\n  database = \"neondb\",\n  disabled = false,\n  className,\n  ...props\n}: SQLRunnerProps) => {\n  const valueControlled = value !== undefined;\n  const modeControlled = mode !== undefined;\n  const [internalValue, setInternalValue] = useState(defaultValue);\n  const [internalMode, setInternalMode] = useState(defaultMode);\n  const [status, setStatus] = useState<\n    \"idle\" | \"running\" | \"success\" | \"error\" | \"blocked\" | \"cancelled\"\n  >(\"idle\");\n  const [result, setResult] = useState<SQLResult | null>(null);\n  const [error, setError] = useState<SQLRunnerError | null>(null);\n  const [lastExecutedQuery, setLastExecutedQuery] = useState(\"\");\n  const [editorState, setEditorState] = useState<{\n    position: EditorPosition;\n    selection: string;\n  }>({\n    position: { column: 1, line: 1, selectedCharacters: 0 },\n    selection: \"\",\n  });\n  const controllerRef = useRef<AbortController | null>(null);\n  const query = valueControlled ? value : internalValue;\n  const safetyMode = modeControlled ? mode : internalMode;\n  const stale = Boolean(\n    result && lastExecutedQuery && query !== lastExecutedQuery\n  );\n\n  const setQuery = (next: string) => {\n    if (!valueControlled) {\n      setInternalValue(next);\n    }\n    onValueChange?.(next);\n    if (status === \"error\" || status === \"blocked\" || status === \"cancelled\") {\n      setStatus(result ? \"success\" : \"idle\");\n      setError(null);\n    }\n  };\n\n  const setMode = (next: SQLRunnerMode) => {\n    if (!modeControlled) {\n      setInternalMode(next);\n    }\n    onModeChange?.(next);\n    if (status === \"blocked\") {\n      setStatus(result ? \"success\" : \"idle\");\n    }\n  };\n\n  const cancel = () => {\n    controllerRef.current?.abort();\n    controllerRef.current = null;\n    setStatus(\"cancelled\");\n  };\n\n  const run = async (selection?: string) => {\n    const trimmed = selection?.trim() || query.trim();\n    if (!trimmed) {\n      setError({ message: \"Enter a query to run.\" });\n      setStatus(\"error\");\n      return;\n    }\n    if (safetyMode === \"read\" && isWriteQuery(trimmed)) {\n      setError(null);\n      setStatus(\"blocked\");\n      return;\n    }\n\n    const controller = new AbortController();\n    controllerRef.current = controller;\n    setError(null);\n    setStatus(\"running\");\n    const startedAt = performance.now();\n\n    try {\n      const next = await onExecute(trimmed, {\n        mode: safetyMode,\n        signal: controller.signal,\n      });\n      if (controller.signal.aborted) {\n        return;\n      }\n      setResult({\n        ...next,\n        durationMs: next.durationMs ?? performance.now() - startedAt,\n      });\n      setLastExecutedQuery(query);\n      setStatus(\"success\");\n    } catch (caughtError) {\n      if (controller.signal.aborted) {\n        return;\n      }\n      setError(toRunnerError(caughtError));\n      setStatus(\"error\");\n    } finally {\n      if (controllerRef.current === controller) {\n        controllerRef.current = null;\n      }\n    }\n  };\n\n  return (\n    <div\n      className={cn(\n        \"@container w-full min-w-0 overflow-hidden rounded-lg border border-border/60 bg-card shadow-xs\",\n        className\n      )}\n      data-mode={safetyMode}\n      data-slot=\"sql-runner\"\n      data-state={status}\n      {...props}\n    >\n      <header className=\"flex min-h-12 flex-wrap items-center gap-2 border-border/60 border-b px-3 py-2\">\n        <span className=\"grid size-7 shrink-0 place-items-center rounded-md bg-primary/10 text-primary\">\n          <HugeiconsIcon\n            aria-hidden=\"true\"\n            className=\"size-3.5\"\n            icon={Database01Icon}\n            strokeWidth={2}\n          />\n        </span>\n        <div className=\"min-w-24 flex-1\">\n          <p className=\"truncate font-medium text-xs\">{title}</p>\n          <p className=\"truncate text-[10px] text-muted-foreground\">\n            {database}\n          </p>\n        </div>\n        <ModeControl mode={safetyMode} onChange={setMode} />\n        {status === \"running\" ? (\n          <Button onClick={cancel} size=\"sm\" variant=\"outline\">\n            <HugeiconsIcon\n              data-icon=\"inline-start\"\n              icon={StopIcon}\n              strokeWidth={2}\n            />\n            Cancel\n          </Button>\n        ) : (\n          <Button\n            disabled={disabled}\n            onClick={() => run(editorState.selection)}\n            size=\"sm\"\n          >\n            <HugeiconsIcon\n              data-icon=\"inline-start\"\n              icon={PlayIcon}\n              strokeWidth={2}\n            />\n            {editorState.selection ? \"Run selection\" : \"Run\"}\n            <kbd className=\"hidden rounded bg-primary-foreground/15 px-1 py-0.5 font-sans text-[9px] @[420px]:inline\">\n              ⌘↵\n            </kbd>\n          </Button>\n        )}\n      </header>\n\n      <Editor\n        disabled={disabled}\n        error={error}\n        onCancel={cancel}\n        onChange={setQuery}\n        onPositionChange={(position, selection) =>\n          setEditorState({ position, selection })\n        }\n        onRun={run}\n        running={status === \"running\"}\n        value={query}\n      />\n      <div className=\"flex h-7 items-center gap-2 border-border/50 border-t bg-muted/15 px-3 text-[10px] text-muted-foreground tabular-nums\">\n        <span>Ln {editorState.position.line}</span>\n        <span>Col {editorState.position.column}</span>\n        {editorState.position.selectedCharacters > 0 ? (\n          <span className=\"rounded-sm bg-primary/10 px-1.5 py-0.5 text-primary\">\n            {editorState.position.selectedCharacters} selected\n          </span>\n        ) : null}\n        <span className=\"ml-auto\">PostgreSQL</span>\n      </div>\n\n      {status === \"blocked\" ? (\n        <BlockedWrite onEnable={() => setMode(\"read-write\")} />\n      ) : null}\n      {status === \"error\" && error ? <ErrorPanel error={error} /> : null}\n\n      <div className=\"border-border/60 border-t\" data-slot=\"sql-runner-results\">\n        <div className=\"flex h-9 items-center gap-2 border-border/50 border-b px-3\">\n          <p className=\"font-medium text-[11px]\">Results</p>\n          <div\n            aria-live=\"polite\"\n            className=\"ml-auto flex items-center gap-2 text-[10px] text-muted-foreground\"\n          >\n            {stale ? (\n              <span className=\"rounded-sm bg-[var(--status-scaling)]/10 px-1.5 py-0.5 text-[var(--status-scaling)]\">\n                Previous query\n              </span>\n            ) : null}\n            {status === \"running\" ? (\n              <span className=\"flex items-center gap-1\">\n                <HugeiconsIcon\n                  aria-hidden=\"true\"\n                  className=\"size-3 animate-spin motion-reduce:animate-none\"\n                  icon={Loading03Icon}\n                  strokeWidth={2}\n                />\n                Running\n              </span>\n            ) : null}\n            {status === \"cancelled\" ? <span>Cancelled</span> : null}\n            {result && status !== \"running\" ? (\n              <>\n                <span className=\"tabular-nums\">\n                  {result.rowCount ?? result.rows.length}{\" \"}\n                  {(result.rowCount ?? result.rows.length) === 1\n                    ? \"row\"\n                    : \"rows\"}\n                </span>\n                {result.durationMs === undefined ? null : (\n                  <span className=\"flex items-center gap-1 tabular-nums\">\n                    <HugeiconsIcon\n                      aria-hidden=\"true\"\n                      className=\"size-3\"\n                      icon={Clock01Icon}\n                      strokeWidth={2}\n                    />\n                    {formatDuration(result.durationMs)}\n                  </span>\n                )}\n              </>\n            ) : null}\n          </div>\n        </div>\n\n        {result ? (\n          <ResultsGrid result={result} />\n        ) : (\n          <div className=\"grid min-h-28 place-items-center px-5 py-7 text-center\">\n            <div>\n              <p className=\"text-[11px] text-muted-foreground\">\n                Run a query to see results.\n              </p>\n              <p className=\"mt-1 text-[10px] text-muted-foreground/60\">\n                Read-only mode blocks statements that can change data.\n              </p>\n            </div>\n          </div>\n        )}\n      </div>\n    </div>\n  );\n};\n",
      "type": "registry:component"
    },
    {
      "path": "src/components/sql-runner/use-sql-runner.ts",
      "content": "\"use client\";\n\nimport type {\n  SQLExecutionContext,\n  SQLResult,\n} from \"@/components/sql-runner/sql-runner\";\n\nexport interface UseSQLRunnerOptions {\n  /** Server endpoint that executes authorized SQL. */\n  endpoint?: string;\n}\n\n/** Fetch adapter for SQLRunner. The AbortSignal cancels the HTTP request. */\nexport const useSQLRunner = ({\n  endpoint = \"/api/sql\",\n}: UseSQLRunnerOptions = {}) => {\n  const execute = async (\n    query: string,\n    context: SQLExecutionContext\n  ): Promise<SQLResult> => {\n    const response = await fetch(endpoint, {\n      body: JSON.stringify({ mode: context.mode, query }),\n      headers: { \"content-type\": \"application/json\" },\n      method: \"POST\",\n      signal: context.signal,\n    });\n\n    const payload = (await response.json()) as SQLResult | { error: string };\n    if (!response.ok || \"error\" in payload) {\n      throw new Error(\"error\" in payload ? payload.error : \"Query failed.\");\n    }\n    return payload;\n  };\n\n  return { execute };\n};\n",
      "type": "registry:hook"
    }
  ],
  "type": "registry:component"
}