@agent-native/core 0.26.9 → 0.28.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (225) hide show
  1. package/dist/agent/run-ownership.d.ts +12 -0
  2. package/dist/agent/run-ownership.d.ts.map +1 -0
  3. package/dist/agent/run-ownership.js +39 -0
  4. package/dist/agent/run-ownership.js.map +1 -0
  5. package/dist/client/AgentPanel.d.ts.map +1 -1
  6. package/dist/client/AgentPanel.js +1 -0
  7. package/dist/client/AgentPanel.js.map +1 -1
  8. package/dist/client/AssistantChat.d.ts.map +1 -1
  9. package/dist/client/AssistantChat.js +9 -6
  10. package/dist/client/AssistantChat.js.map +1 -1
  11. package/dist/client/db-admin/DataGrid.d.ts +42 -0
  12. package/dist/client/db-admin/DataGrid.d.ts.map +1 -0
  13. package/dist/client/db-admin/DataGrid.js +204 -0
  14. package/dist/client/db-admin/DataGrid.js.map +1 -0
  15. package/dist/client/db-admin/DbAdminPage.d.ts +2 -0
  16. package/dist/client/db-admin/DbAdminPage.d.ts.map +1 -0
  17. package/dist/client/db-admin/DbAdminPage.js +72 -0
  18. package/dist/client/db-admin/DbAdminPage.js.map +1 -0
  19. package/dist/client/db-admin/DevDatabaseLink.d.ts +19 -0
  20. package/dist/client/db-admin/DevDatabaseLink.d.ts.map +1 -0
  21. package/dist/client/db-admin/DevDatabaseLink.js +25 -0
  22. package/dist/client/db-admin/DevDatabaseLink.js.map +1 -0
  23. package/dist/client/db-admin/EditableCell.d.ts +26 -0
  24. package/dist/client/db-admin/EditableCell.d.ts.map +1 -0
  25. package/dist/client/db-admin/EditableCell.js +150 -0
  26. package/dist/client/db-admin/EditableCell.js.map +1 -0
  27. package/dist/client/db-admin/FilterBar.d.ts +8 -0
  28. package/dist/client/db-admin/FilterBar.d.ts.map +1 -0
  29. package/dist/client/db-admin/FilterBar.js +68 -0
  30. package/dist/client/db-admin/FilterBar.js.map +1 -0
  31. package/dist/client/db-admin/ResultsGrid.d.ts +6 -0
  32. package/dist/client/db-admin/ResultsGrid.d.ts.map +1 -0
  33. package/dist/client/db-admin/ResultsGrid.js +41 -0
  34. package/dist/client/db-admin/ResultsGrid.js.map +1 -0
  35. package/dist/client/db-admin/RowSidePanel.d.ts +18 -0
  36. package/dist/client/db-admin/RowSidePanel.d.ts.map +1 -0
  37. package/dist/client/db-admin/RowSidePanel.js +104 -0
  38. package/dist/client/db-admin/RowSidePanel.js.map +1 -0
  39. package/dist/client/db-admin/SqlEditor.d.ts +8 -0
  40. package/dist/client/db-admin/SqlEditor.d.ts.map +1 -0
  41. package/dist/client/db-admin/SqlEditor.js +350 -0
  42. package/dist/client/db-admin/SqlEditor.js.map +1 -0
  43. package/dist/client/db-admin/TableBrowser.d.ts +10 -0
  44. package/dist/client/db-admin/TableBrowser.d.ts.map +1 -0
  45. package/dist/client/db-admin/TableBrowser.js +61 -0
  46. package/dist/client/db-admin/TableBrowser.js.map +1 -0
  47. package/dist/client/db-admin/TableEditor.d.ts +9 -0
  48. package/dist/client/db-admin/TableEditor.d.ts.map +1 -0
  49. package/dist/client/db-admin/TableEditor.js +254 -0
  50. package/dist/client/db-admin/TableEditor.js.map +1 -0
  51. package/dist/client/db-admin/cell-format.d.ts +55 -0
  52. package/dist/client/db-admin/cell-format.d.ts.map +1 -0
  53. package/dist/client/db-admin/cell-format.js +223 -0
  54. package/dist/client/db-admin/cell-format.js.map +1 -0
  55. package/dist/client/db-admin/changeset.d.ts +74 -0
  56. package/dist/client/db-admin/changeset.d.ts.map +1 -0
  57. package/dist/client/db-admin/changeset.js +169 -0
  58. package/dist/client/db-admin/changeset.js.map +1 -0
  59. package/dist/client/db-admin/export-utils.d.ts +15 -0
  60. package/dist/client/db-admin/export-utils.d.ts.map +1 -0
  61. package/dist/client/db-admin/export-utils.js +62 -0
  62. package/dist/client/db-admin/export-utils.js.map +1 -0
  63. package/dist/client/db-admin/index.d.ts +7 -0
  64. package/dist/client/db-admin/index.d.ts.map +1 -0
  65. package/dist/client/db-admin/index.js +8 -0
  66. package/dist/client/db-admin/index.js.map +1 -0
  67. package/dist/client/db-admin/sql-storage.d.ts +35 -0
  68. package/dist/client/db-admin/sql-storage.d.ts.map +1 -0
  69. package/dist/client/db-admin/sql-storage.js +117 -0
  70. package/dist/client/db-admin/sql-storage.js.map +1 -0
  71. package/dist/client/db-admin/storage.d.ts +24 -0
  72. package/dist/client/db-admin/storage.d.ts.map +1 -0
  73. package/dist/client/db-admin/storage.js +50 -0
  74. package/dist/client/db-admin/storage.js.map +1 -0
  75. package/dist/client/db-admin/useAgentSync.d.ts +22 -0
  76. package/dist/client/db-admin/useAgentSync.d.ts.map +1 -0
  77. package/dist/client/db-admin/useAgentSync.js +120 -0
  78. package/dist/client/db-admin/useAgentSync.js.map +1 -0
  79. package/dist/client/db-admin/useDbAdmin.d.ts +20 -0
  80. package/dist/client/db-admin/useDbAdmin.d.ts.map +1 -0
  81. package/dist/client/db-admin/useDbAdmin.js +154 -0
  82. package/dist/client/db-admin/useDbAdmin.js.map +1 -0
  83. package/dist/client/index.d.ts +2 -1
  84. package/dist/client/index.d.ts.map +1 -1
  85. package/dist/client/index.js +2 -1
  86. package/dist/client/index.js.map +1 -1
  87. package/dist/client/settings/SettingsPanel.d.ts.map +1 -1
  88. package/dist/client/settings/SettingsPanel.js +8 -4
  89. package/dist/client/settings/SettingsPanel.js.map +1 -1
  90. package/dist/client/use-dev-mode.d.ts +20 -2
  91. package/dist/client/use-dev-mode.d.ts.map +1 -1
  92. package/dist/client/use-dev-mode.js +49 -14
  93. package/dist/client/use-dev-mode.js.map +1 -1
  94. package/dist/credentials/index.d.ts.map +1 -1
  95. package/dist/credentials/index.js +25 -5
  96. package/dist/credentials/index.js.map +1 -1
  97. package/dist/db-admin/agent-tools.d.ts +15 -0
  98. package/dist/db-admin/agent-tools.d.ts.map +1 -0
  99. package/dist/db-admin/agent-tools.js +147 -0
  100. package/dist/db-admin/agent-tools.js.map +1 -0
  101. package/dist/db-admin/operations.d.ts +17 -0
  102. package/dist/db-admin/operations.d.ts.map +1 -0
  103. package/dist/db-admin/operations.js +541 -0
  104. package/dist/db-admin/operations.js.map +1 -0
  105. package/dist/db-admin/routes.d.ts +5 -0
  106. package/dist/db-admin/routes.d.ts.map +1 -0
  107. package/dist/db-admin/routes.js +134 -0
  108. package/dist/db-admin/routes.js.map +1 -0
  109. package/dist/db-admin/types.d.ts +85 -0
  110. package/dist/db-admin/types.d.ts.map +1 -0
  111. package/dist/db-admin/types.js +9 -0
  112. package/dist/db-admin/types.js.map +1 -0
  113. package/dist/extensions/url-safety.d.ts +20 -0
  114. package/dist/extensions/url-safety.d.ts.map +1 -1
  115. package/dist/extensions/url-safety.js +43 -0
  116. package/dist/extensions/url-safety.js.map +1 -1
  117. package/dist/file-upload/actions/upload-image.d.ts.map +1 -1
  118. package/dist/file-upload/actions/upload-image.js +6 -1
  119. package/dist/file-upload/actions/upload-image.js.map +1 -1
  120. package/dist/integrations/adapters/email.d.ts.map +1 -1
  121. package/dist/integrations/adapters/email.js +112 -0
  122. package/dist/integrations/adapters/email.js.map +1 -1
  123. package/dist/integrations/types.d.ts +11 -0
  124. package/dist/integrations/types.d.ts.map +1 -1
  125. package/dist/integrations/types.js.map +1 -1
  126. package/dist/scripts/db/exec.d.ts.map +1 -1
  127. package/dist/scripts/db/exec.js +2 -1
  128. package/dist/scripts/db/exec.js.map +1 -1
  129. package/dist/scripts/db/index.d.ts.map +1 -1
  130. package/dist/scripts/db/index.js +1 -0
  131. package/dist/scripts/db/index.js.map +1 -1
  132. package/dist/scripts/db/migrate-encrypt-credentials.d.ts +28 -0
  133. package/dist/scripts/db/migrate-encrypt-credentials.d.ts.map +1 -0
  134. package/dist/scripts/db/migrate-encrypt-credentials.js +190 -0
  135. package/dist/scripts/db/migrate-encrypt-credentials.js.map +1 -0
  136. package/dist/scripts/db/query.d.ts.map +1 -1
  137. package/dist/scripts/db/query.js +2 -1
  138. package/dist/scripts/db/query.js.map +1 -1
  139. package/dist/scripts/db/safety.d.ts +1 -0
  140. package/dist/scripts/db/safety.d.ts.map +1 -1
  141. package/dist/scripts/db/safety.js +32 -0
  142. package/dist/scripts/db/safety.js.map +1 -1
  143. package/dist/scripts/db/scoping.d.ts.map +1 -1
  144. package/dist/scripts/db/scoping.js +11 -1
  145. package/dist/scripts/db/scoping.js.map +1 -1
  146. package/dist/secrets/crypto.d.ts +28 -0
  147. package/dist/secrets/crypto.d.ts.map +1 -0
  148. package/dist/secrets/crypto.js +81 -0
  149. package/dist/secrets/crypto.js.map +1 -0
  150. package/dist/secrets/storage.d.ts.map +1 -1
  151. package/dist/secrets/storage.js +3 -61
  152. package/dist/secrets/storage.js.map +1 -1
  153. package/dist/server/action-discovery.d.ts.map +1 -1
  154. package/dist/server/action-discovery.js +5 -2
  155. package/dist/server/action-discovery.js.map +1 -1
  156. package/dist/server/action-routes.d.ts.map +1 -1
  157. package/dist/server/action-routes.js +24 -7
  158. package/dist/server/action-routes.js.map +1 -1
  159. package/dist/server/agent-chat-plugin.d.ts.map +1 -1
  160. package/dist/server/agent-chat-plugin.js +49 -2
  161. package/dist/server/agent-chat-plugin.js.map +1 -1
  162. package/dist/server/auth.d.ts +1 -1
  163. package/dist/server/auth.d.ts.map +1 -1
  164. package/dist/server/auth.js.map +1 -1
  165. package/dist/server/better-auth-instance.js +3 -3
  166. package/dist/server/better-auth-instance.js.map +1 -1
  167. package/dist/server/core-routes-plugin.d.ts.map +1 -1
  168. package/dist/server/core-routes-plugin.js +5 -0
  169. package/dist/server/core-routes-plugin.js.map +1 -1
  170. package/dist/server/csrf.d.ts.map +1 -1
  171. package/dist/server/csrf.js +9 -1
  172. package/dist/server/csrf.js.map +1 -1
  173. package/dist/server/design-token-utils.d.ts +8 -1
  174. package/dist/server/design-token-utils.d.ts.map +1 -1
  175. package/dist/server/design-token-utils.js +12 -4
  176. package/dist/server/design-token-utils.js.map +1 -1
  177. package/dist/templates/default/AGENTS.md +4 -4
  178. package/dist/templates/default/app/routes/database.tsx +13 -0
  179. package/dist/templates/workspace-core/.agents/skills/authentication/SKILL.md +9 -2
  180. package/dist/templates/workspace-core/.agents/skills/sharing/SKILL.md +7 -1
  181. package/dist/vite/client.d.ts.map +1 -1
  182. package/dist/vite/client.js +4 -0
  183. package/dist/vite/client.js.map +1 -1
  184. package/docs/content/a2a-protocol.md +2 -2
  185. package/docs/content/actions.md +2 -54
  186. package/docs/content/agent-mentions.md +1 -1
  187. package/docs/content/agent-teams.md +1 -1
  188. package/docs/content/authentication.md +2 -2
  189. package/docs/content/cli-adapters.md +33 -17
  190. package/docs/content/client.md +11 -20
  191. package/docs/content/code-agents-ui.md +19 -6
  192. package/docs/content/context-awareness.md +36 -20
  193. package/docs/content/database.md +3 -3
  194. package/docs/content/deployment.md +8 -8
  195. package/docs/content/dispatch.md +1 -1
  196. package/docs/content/external-agents.md +5 -1
  197. package/docs/content/faq.md +1 -0
  198. package/docs/content/frames.md +116 -30
  199. package/docs/content/getting-started.md +15 -14
  200. package/docs/content/mcp-clients.md +1 -1
  201. package/docs/content/mcp-protocol.md +11 -88
  202. package/docs/content/messaging.md +1 -1
  203. package/docs/content/migration-workbench.md +13 -87
  204. package/docs/content/multi-app-workspace.md +2 -38
  205. package/docs/content/multi-tenancy.md +3 -26
  206. package/docs/content/onboarding.md +10 -3
  207. package/docs/content/recurring-jobs.md +2 -2
  208. package/docs/content/security.md +33 -1
  209. package/docs/content/server.md +1 -1
  210. package/docs/content/template-assets.md +9 -9
  211. package/docs/content/template-brain.md +114 -388
  212. package/docs/content/template-clips.md +42 -2
  213. package/docs/content/template-content.md +1 -1
  214. package/docs/content/template-design.md +27 -0
  215. package/docs/content/template-dispatch.md +3 -3
  216. package/docs/content/template-forms.md +6 -6
  217. package/docs/content/template-starter.md +2 -2
  218. package/docs/content/using-your-agent.md +56 -0
  219. package/docs/content/workspace-management.md +6 -6
  220. package/docs/content/workspace.md +28 -9
  221. package/package.json +10 -3
  222. package/src/templates/default/AGENTS.md +4 -4
  223. package/src/templates/default/app/routes/database.tsx +13 -0
  224. package/src/templates/workspace-core/.agents/skills/authentication/SKILL.md +9 -2
  225. package/src/templates/workspace-core/.agents/skills/sharing/SKILL.md +7 -1
@@ -0,0 +1,350 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /**
3
+ * Production-grade SQL editor for the dev-mode database admin.
4
+ *
5
+ * Layout: a CodeMirror editor pane on top, a resizable results panel below.
6
+ * Features schema-aware autocomplete, keyboard run shortcuts, history, named
7
+ * snippets, CSV/JSON export, and a confirm modal for destructive statements.
8
+ *
9
+ * Data access goes through `runQuery` from `./useDbAdmin.js` (the shared
10
+ * contract). On a destructive statement without confirmation, `runQuery` throws
11
+ * an Error whose `needsConfirm` flag is `true`; we catch that and re-run after
12
+ * the user confirms in a locally-built modal.
13
+ */
14
+ import { useCallback, useEffect, useMemo, useRef, useState, } from "react";
15
+ import CodeMirror, { EditorView, keymap, Prec, } from "@uiw/react-codemirror";
16
+ import { sql, PostgreSQL } from "@codemirror/lang-sql";
17
+ import { oneDark } from "@codemirror/theme-one-dark";
18
+ import { IconPlayerPlayFilled, IconHistory, IconBookmark, IconBookmarkPlus, IconDownload, IconAlertTriangle, IconLoader2, IconTrash, IconX, IconChevronDown, } from "@tabler/icons-react";
19
+ import { cn } from "../utils.js";
20
+ import { Popover, PopoverContent, PopoverTrigger, } from "../components/ui/popover.js";
21
+ import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, } from "../components/ui/dropdown-menu.js";
22
+ import { runQuery } from "./useDbAdmin.js";
23
+ import { ResultsGrid } from "./ResultsGrid.js";
24
+ import { toCSV, toJSON, downloadFile } from "./export-utils.js";
25
+ import { loadHistory, pushHistory, clearHistory, loadSnippets, saveSnippet, deleteSnippet, } from "./sql-storage.js";
26
+ // ─── Dark-mode detection ─────────────────────────────────────────────────────
27
+ function useIsDark() {
28
+ const [isDark, setIsDark] = useState(false);
29
+ useEffect(() => {
30
+ if (typeof document === "undefined")
31
+ return;
32
+ const root = document.documentElement;
33
+ const update = () => setIsDark(root.classList.contains("dark"));
34
+ update();
35
+ const observer = new MutationObserver(update);
36
+ observer.observe(root, { attributes: true, attributeFilter: ["class"] });
37
+ return () => observer.disconnect();
38
+ }, []);
39
+ return isDark;
40
+ }
41
+ // ─── Statement-under-cursor helpers ──────────────────────────────────────────
42
+ /**
43
+ * Best-effort split of a SQL buffer into statements by top-level `;`, ignoring
44
+ * semicolons inside single/double quotes or line/block comments. Returns each
45
+ * statement with its character offsets so we can pick the one under the cursor.
46
+ */
47
+ function splitStatements(text) {
48
+ const out = [];
49
+ let start = 0;
50
+ let inSingle = false;
51
+ let inDouble = false;
52
+ let inLineComment = false;
53
+ let inBlockComment = false;
54
+ for (let i = 0; i < text.length; i++) {
55
+ const ch = text[i];
56
+ const next = text[i + 1];
57
+ if (inLineComment) {
58
+ if (ch === "\n")
59
+ inLineComment = false;
60
+ continue;
61
+ }
62
+ if (inBlockComment) {
63
+ if (ch === "*" && next === "/") {
64
+ inBlockComment = false;
65
+ i++;
66
+ }
67
+ continue;
68
+ }
69
+ if (inSingle) {
70
+ if (ch === "'")
71
+ inSingle = false;
72
+ continue;
73
+ }
74
+ if (inDouble) {
75
+ if (ch === '"')
76
+ inDouble = false;
77
+ continue;
78
+ }
79
+ if (ch === "-" && next === "-") {
80
+ inLineComment = true;
81
+ i++;
82
+ continue;
83
+ }
84
+ if (ch === "/" && next === "*") {
85
+ inBlockComment = true;
86
+ i++;
87
+ continue;
88
+ }
89
+ if (ch === "'") {
90
+ inSingle = true;
91
+ continue;
92
+ }
93
+ if (ch === '"') {
94
+ inDouble = true;
95
+ continue;
96
+ }
97
+ if (ch === ";") {
98
+ out.push({ sql: text.slice(start, i + 1), start, end: i + 1 });
99
+ start = i + 1;
100
+ }
101
+ }
102
+ if (start < text.length) {
103
+ out.push({ sql: text.slice(start), start, end: text.length });
104
+ }
105
+ return out;
106
+ }
107
+ /** Resolve which SQL to run given the current selection and cursor position. */
108
+ function resolveRunTarget(view, buffer) {
109
+ if (!view)
110
+ return buffer;
111
+ const sel = view.state.selection.main;
112
+ if (!sel.empty) {
113
+ return view.state.sliceDoc(sel.from, sel.to);
114
+ }
115
+ const cursor = sel.head;
116
+ const statements = splitStatements(buffer);
117
+ const hit = statements.find((s) => cursor >= s.start && cursor <= s.end);
118
+ return (hit?.sql ?? buffer).trim() || buffer;
119
+ }
120
+ // ─── Local modal primitive ───────────────────────────────────────────────────
121
+ function Modal({ title, children, onClose, }) {
122
+ useEffect(() => {
123
+ const onKey = (e) => {
124
+ if (e.key === "Escape")
125
+ onClose();
126
+ };
127
+ window.addEventListener("keydown", onKey);
128
+ return () => window.removeEventListener("keydown", onKey);
129
+ }, [onClose]);
130
+ return (_jsxs("div", { className: "fixed inset-0 z-50 flex items-center justify-center p-4", children: [_jsx("div", { className: "absolute inset-0 bg-black/50", onClick: onClose, "aria-hidden": true }), _jsxs("div", { className: "relative z-10 w-full max-w-md rounded-lg border border-border bg-background shadow-xl", children: [_jsxs("div", { className: "flex items-center justify-between border-b border-border px-4 py-3", children: [_jsx("h2", { className: "text-sm font-semibold text-foreground", children: title }), _jsx("button", { type: "button", onClick: onClose, className: "cursor-pointer rounded p-1 text-muted-foreground hover:bg-accent hover:text-foreground", "aria-label": "Close", children: _jsx(IconX, { size: 16 }) })] }), _jsx("div", { className: "p-4", children: children })] })] }));
131
+ }
132
+ // ─── Toolbar button ──────────────────────────────────────────────────────────
133
+ function ToolbarButton({ children, onClick, className, title, disabled, }) {
134
+ return (_jsx("button", { type: "button", onClick: onClick, title: title, disabled: disabled, className: cn("inline-flex h-8 cursor-pointer items-center gap-1.5 rounded-md border border-border bg-background px-2.5 text-xs font-medium text-foreground transition-colors hover:bg-accent disabled:cursor-not-allowed disabled:opacity-50", className), children: children }));
135
+ }
136
+ const isMac = typeof navigator !== "undefined" &&
137
+ /Mac|iPhone|iPad/.test(navigator.platform);
138
+ const MOD = isMac ? "Cmd" : "Ctrl";
139
+ // ─── Main component ──────────────────────────────────────────────────────────
140
+ export function SqlEditor({ dialect, tableNames, columnsByTable, }) {
141
+ const isDark = useIsDark();
142
+ const editorRef = useRef(null);
143
+ const [value, setValue] = useState("");
144
+ const [running, setRunning] = useState(false);
145
+ const [result, setResult] = useState(null);
146
+ const [error, setError] = useState(null);
147
+ const [status, setStatus] = useState(null);
148
+ const [history, setHistory] = useState([]);
149
+ const [snippets, setSnippets] = useState([]);
150
+ const [confirmSql, setConfirmSql] = useState(null);
151
+ const [saveModalOpen, setSaveModalOpen] = useState(false);
152
+ const [snippetName, setSnippetName] = useState("");
153
+ // Editor height (px) — draggable splitter between editor and results.
154
+ const [editorHeight, setEditorHeight] = useState(240);
155
+ const dragState = useRef(null);
156
+ // Keep the latest buffer accessible inside CodeMirror keymap closures.
157
+ const valueRef = useRef(value);
158
+ valueRef.current = value;
159
+ useEffect(() => {
160
+ setHistory(loadHistory());
161
+ setSnippets(loadSnippets());
162
+ }, []);
163
+ // ─── Run logic ────────────────────────────────────────────────────────────
164
+ const execute = useCallback(async (sqlText, confirmDestructive) => {
165
+ const trimmed = sqlText.trim();
166
+ if (!trimmed)
167
+ return;
168
+ setRunning(true);
169
+ setError(null);
170
+ try {
171
+ const res = await runQuery(trimmed, undefined, confirmDestructive);
172
+ setResult({
173
+ columns: res.columns,
174
+ rows: res.rows,
175
+ rowsAffected: res.rowsAffected,
176
+ durationMs: res.durationMs,
177
+ });
178
+ if (res.columns.length > 0) {
179
+ setStatus(`${res.rows.length} rows · ${res.durationMs}ms`);
180
+ }
181
+ else {
182
+ setStatus(`Query OK · ${res.rowsAffected} rows affected · ${res.durationMs}ms`);
183
+ }
184
+ setHistory(pushHistory(trimmed));
185
+ }
186
+ catch (err) {
187
+ const e = err;
188
+ if (e.needsConfirm) {
189
+ setConfirmSql(trimmed);
190
+ }
191
+ else {
192
+ setError(e.message || "Query failed");
193
+ setStatus(null);
194
+ }
195
+ }
196
+ finally {
197
+ setRunning(false);
198
+ }
199
+ }, []);
200
+ const runActiveStatement = useCallback(() => {
201
+ const view = editorRef.current?.view;
202
+ const target = resolveRunTarget(view, valueRef.current);
203
+ void execute(target);
204
+ }, [execute]);
205
+ const runWholeBuffer = useCallback(() => {
206
+ void execute(valueRef.current);
207
+ }, [execute]);
208
+ const confirmAndRun = useCallback(() => {
209
+ const sqlText = confirmSql;
210
+ setConfirmSql(null);
211
+ if (sqlText)
212
+ void execute(sqlText, true);
213
+ }, [confirmSql, execute]);
214
+ // ─── CodeMirror extensions ──────────────────────────────────────────────
215
+ const extensions = useMemo(() => {
216
+ const langExt = sql({
217
+ dialect: dialect === "postgres" ? PostgreSQL : undefined,
218
+ schema: columnsByTable,
219
+ tables: tableNames.map((t) => ({ label: t })),
220
+ upperCaseKeywords: true,
221
+ });
222
+ const runKeymap = Prec.highest(keymap.of([
223
+ {
224
+ key: "Mod-Enter",
225
+ preventDefault: true,
226
+ run: () => {
227
+ runActiveStatement();
228
+ return true;
229
+ },
230
+ },
231
+ {
232
+ key: "Mod-Shift-Enter",
233
+ preventDefault: true,
234
+ run: () => {
235
+ runWholeBuffer();
236
+ return true;
237
+ },
238
+ },
239
+ ]));
240
+ return [runKeymap, langExt, EditorView.lineWrapping];
241
+ // tableNames / columnsByTable identity is stable enough for our purposes;
242
+ // re-derive when the dialect or schema reference changes.
243
+ }, [dialect, columnsByTable, tableNames, runActiveStatement, runWholeBuffer]);
244
+ // ─── Splitter drag ───────────────────────────────────────────────────────
245
+ useEffect(() => {
246
+ const onMove = (e) => {
247
+ if (!dragState.current)
248
+ return;
249
+ const delta = e.clientY - dragState.current.startY;
250
+ const next = Math.min(Math.max(dragState.current.startHeight + delta, 120), 640);
251
+ setEditorHeight(next);
252
+ };
253
+ const onUp = () => {
254
+ dragState.current = null;
255
+ document.body.style.userSelect = "";
256
+ };
257
+ window.addEventListener("mousemove", onMove);
258
+ window.addEventListener("mouseup", onUp);
259
+ return () => {
260
+ window.removeEventListener("mousemove", onMove);
261
+ window.removeEventListener("mouseup", onUp);
262
+ };
263
+ }, []);
264
+ const startDrag = (e) => {
265
+ dragState.current = { startY: e.clientY, startHeight: editorHeight };
266
+ document.body.style.userSelect = "none";
267
+ };
268
+ // ─── Loading editor content from history / snippets ───────────────────────
269
+ const loadIntoEditor = useCallback((sqlText) => {
270
+ setValue(sqlText);
271
+ // Focus and place cursor at end after the controlled value updates.
272
+ requestAnimationFrame(() => {
273
+ const view = editorRef.current?.view;
274
+ if (view) {
275
+ view.focus();
276
+ view.dispatch({ selection: { anchor: view.state.doc.length } });
277
+ }
278
+ });
279
+ }, []);
280
+ // ─── Export ────────────────────────────────────────────────────────────────
281
+ const exportAs = (format) => {
282
+ if (!result || result.columns.length === 0)
283
+ return;
284
+ const stamp = new Date().toISOString().replace(/[:.]/g, "-").slice(0, 19);
285
+ if (format === "csv") {
286
+ downloadFile(`query-result-${stamp}.csv`, "text/csv;charset=utf-8", toCSV(result.columns, result.rows));
287
+ }
288
+ else {
289
+ downloadFile(`query-result-${stamp}.json`, "application/json", toJSON(result.rows));
290
+ }
291
+ };
292
+ // ─── Snippet save ────────────────────────────────────────────────────────
293
+ const openSaveModal = () => {
294
+ if (!value.trim())
295
+ return;
296
+ setSnippetName("");
297
+ setSaveModalOpen(true);
298
+ };
299
+ const commitSnippet = () => {
300
+ const name = snippetName.trim();
301
+ if (!name || !value.trim())
302
+ return;
303
+ setSnippets(saveSnippet({ name, sql: value }));
304
+ setSaveModalOpen(false);
305
+ };
306
+ const removeSnippet = (id) => {
307
+ setSnippets(deleteSnippet(id));
308
+ };
309
+ // ─── Example queries (empty state) ─────────────────────────────────────────
310
+ const firstTable = tableNames[0];
311
+ const examples = useMemo(() => {
312
+ const list = [];
313
+ if (firstTable) {
314
+ list.push({
315
+ label: `Select rows from ${firstTable}`,
316
+ sql: `SELECT * FROM ${firstTable} LIMIT 100;`,
317
+ });
318
+ }
319
+ list.push({
320
+ label: "List tables",
321
+ sql: dialect === "postgres"
322
+ ? "SELECT table_name FROM information_schema.tables WHERE table_schema = 'public' ORDER BY table_name;"
323
+ : "SELECT name FROM sqlite_master WHERE type = 'table' ORDER BY name;",
324
+ });
325
+ if (firstTable) {
326
+ list.push({
327
+ label: `Count rows in ${firstTable}`,
328
+ sql: `SELECT COUNT(*) AS total FROM ${firstTable};`,
329
+ });
330
+ }
331
+ return list;
332
+ }, [firstTable, dialect]);
333
+ const hasResults = result !== null;
334
+ const canExport = hasResults && result.columns.length > 0;
335
+ // ─── Render ──────────────────────────────────────────────────────────────
336
+ return (_jsxs("div", { className: "flex h-full flex-col", children: [_jsxs("div", { className: "flex flex-wrap items-center gap-2 border-b border-border bg-background px-3 py-2", children: [_jsxs("button", { type: "button", onClick: runActiveStatement, disabled: running || !value.trim(), title: `Run selection / statement (${MOD}+Enter)`, className: "inline-flex h-8 cursor-pointer items-center gap-1.5 rounded-md bg-primary px-3 text-xs font-semibold text-primary-foreground transition-colors hover:bg-primary/90 disabled:cursor-not-allowed disabled:opacity-50", children: [running ? (_jsx(IconLoader2, { size: 14, className: "animate-spin" })) : (_jsx(IconPlayerPlayFilled, { size: 14 })), "Run"] }), _jsxs("span", { className: "hidden text-[11px] text-muted-foreground sm:inline", children: [MOD, "+Enter runs selection / statement \u00B7 ", MOD, "+Shift+Enter runs all"] }), _jsxs("div", { className: "ml-auto flex items-center gap-2", children: [_jsxs(Popover, { children: [_jsx(PopoverTrigger, { asChild: true, children: _jsxs(ToolbarButton, { title: "Query history", children: [_jsx(IconHistory, { size: 14 }), "History"] }) }), _jsxs(PopoverContent, { align: "end", className: "w-96 p-0", children: [_jsxs("div", { className: "flex items-center justify-between border-b border-border px-3 py-2", children: [_jsx("span", { className: "text-xs font-semibold text-foreground", children: "Recent queries" }), history.length > 0 && (_jsx("button", { type: "button", onClick: () => {
337
+ clearHistory();
338
+ setHistory([]);
339
+ }, className: "cursor-pointer text-[11px] text-muted-foreground hover:text-foreground", children: "Clear" }))] }), _jsx("div", { className: "max-h-80 overflow-auto py-1", children: history.length === 0 ? (_jsx("div", { className: "px-3 py-6 text-center text-xs text-muted-foreground", children: "No queries yet" })) : (history.map((h, i) => (_jsx("button", { type: "button", onClick: () => loadIntoEditor(h), className: "block w-full cursor-pointer truncate px-3 py-1.5 text-left font-mono text-[11px] text-foreground hover:bg-accent", title: h, children: h.replace(/\s+/g, " ").trim() }, i)))) })] })] }), _jsxs(Popover, { children: [_jsx(PopoverTrigger, { asChild: true, children: _jsxs(ToolbarButton, { title: "Saved snippets", children: [_jsx(IconBookmark, { size: 14 }), "Snippets"] }) }), _jsxs(PopoverContent, { align: "end", className: "w-96 p-0", children: [_jsxs("div", { className: "flex items-center justify-between border-b border-border px-3 py-2", children: [_jsx("span", { className: "text-xs font-semibold text-foreground", children: "Saved snippets" }), _jsxs("button", { type: "button", onClick: openSaveModal, disabled: !value.trim(), className: "inline-flex cursor-pointer items-center gap-1 text-[11px] text-muted-foreground hover:text-foreground disabled:cursor-not-allowed disabled:opacity-50", children: [_jsx(IconBookmarkPlus, { size: 13 }), "Save current"] })] }), _jsx("div", { className: "max-h-80 overflow-auto py-1", children: snippets.length === 0 ? (_jsx("div", { className: "px-3 py-6 text-center text-xs text-muted-foreground", children: "No saved snippets" })) : (snippets.map((s) => (_jsxs("div", { className: "group flex items-center gap-2 px-3 py-1.5 hover:bg-accent", children: [_jsxs("button", { type: "button", onClick: () => loadIntoEditor(s.sql), className: "min-w-0 flex-1 cursor-pointer text-left", title: s.sql, children: [_jsx("div", { className: "truncate text-xs font-medium text-foreground", children: s.name }), _jsx("div", { className: "truncate font-mono text-[10px] text-muted-foreground", children: s.sql.replace(/\s+/g, " ").trim() })] }), _jsx("button", { type: "button", onClick: () => removeSnippet(s.id), className: "cursor-pointer rounded p-1 text-muted-foreground opacity-0 transition-opacity hover:text-red-500 group-hover:opacity-100", title: "Delete snippet", children: _jsx(IconTrash, { size: 13 }) })] }, s.id)))) })] })] }), _jsxs(DropdownMenu, { children: [_jsx(DropdownMenuTrigger, { asChild: true, children: _jsxs(ToolbarButton, { title: "Export results", disabled: !canExport, children: [_jsx(IconDownload, { size: 14 }), "Export", _jsx(IconChevronDown, { size: 12, className: "opacity-60" })] }) }), _jsxs(DropdownMenuContent, { align: "end", children: [_jsx(DropdownMenuItem, { onClick: () => exportAs("csv"), children: "Download CSV" }), _jsx(DropdownMenuItem, { onClick: () => exportAs("json"), children: "Download JSON" })] })] })] })] }), _jsx("div", { className: "shrink-0 overflow-hidden border-b border-border", style: { height: editorHeight }, children: _jsx(CodeMirror, { ref: editorRef, value: value, onChange: setValue, height: `${editorHeight}px`, theme: isDark ? oneDark : undefined, extensions: extensions, placeholder: "Write SQL here\u2026", basicSetup: {
340
+ lineNumbers: true,
341
+ highlightActiveLine: true,
342
+ autocompletion: true,
343
+ bracketMatching: true,
344
+ closeBrackets: true,
345
+ }, style: { fontSize: 13, height: "100%" } }) }), _jsx("div", { onMouseDown: startDrag, className: "h-1.5 shrink-0 cursor-row-resize bg-border/40 transition-colors hover:bg-primary/40", role: "separator", "aria-orientation": "horizontal" }), (status || error) && (_jsx("div", { className: "shrink-0 px-3 py-1.5", children: error ? (_jsxs("div", { className: "flex items-start gap-2 rounded-md border border-red-500/40 bg-red-500/10 px-3 py-2 text-xs text-red-500", children: [_jsx(IconAlertTriangle, { size: 14, className: "mt-px shrink-0" }), _jsx("span", { className: "break-words font-mono", children: error })] })) : (_jsx("div", { className: "font-mono text-[11px] text-muted-foreground", children: status })) })), _jsx("div", { className: "min-h-0 flex-1 overflow-hidden", children: hasResults ? (_jsx(ResultsGrid, { columns: result.columns, rows: result.rows })) : (_jsxs("div", { className: "flex h-full flex-col items-center justify-center gap-4 p-6 text-center", children: [_jsx("div", { className: "text-sm text-muted-foreground", children: "Run a query to see results, or start with an example:" }), _jsx("div", { className: "flex flex-col gap-2", children: examples.map((ex, i) => (_jsxs("button", { type: "button", onClick: () => loadIntoEditor(ex.sql), className: "cursor-pointer rounded-md border border-border bg-background px-4 py-2 text-left transition-colors hover:bg-accent", children: [_jsx("div", { className: "text-xs font-medium text-foreground", children: ex.label }), _jsx("div", { className: "mt-0.5 font-mono text-[11px] text-muted-foreground", children: ex.sql })] }, i))) })] })) }), confirmSql && (_jsx(Modal, { title: "Confirm destructive query", onClose: () => setConfirmSql(null), children: _jsxs("div", { className: "space-y-4", children: [_jsxs("div", { className: "flex items-start gap-2 text-sm text-foreground", children: [_jsx(IconAlertTriangle, { size: 18, className: "mt-px shrink-0 text-yellow-500" }), _jsx("p", { children: "This looks like a destructive query (DROP / TRUNCATE / DELETE without WHERE). Run it?" })] }), _jsx("pre", { className: "max-h-40 overflow-auto rounded-md border border-border bg-muted/50 p-3 font-mono text-[11px] text-foreground", children: confirmSql }), _jsxs("div", { className: "flex justify-end gap-2", children: [_jsx("button", { type: "button", onClick: () => setConfirmSql(null), className: "inline-flex h-8 cursor-pointer items-center rounded-md border border-border bg-background px-3 text-xs font-medium text-foreground hover:bg-accent", children: "Cancel" }), _jsx("button", { type: "button", onClick: confirmAndRun, className: "inline-flex h-8 cursor-pointer items-center rounded-md bg-red-500 px-3 text-xs font-semibold text-white hover:bg-red-600", children: "Run anyway" })] })] }) })), saveModalOpen && (_jsx(Modal, { title: "Save snippet", onClose: () => setSaveModalOpen(false), children: _jsxs("div", { className: "space-y-4", children: [_jsxs("div", { children: [_jsx("label", { className: "mb-1.5 block text-xs font-medium text-muted-foreground", children: "Snippet name" }), _jsx("input", { autoFocus: true, value: snippetName, onChange: (e) => setSnippetName(e.target.value), onKeyDown: (e) => {
346
+ if (e.key === "Enter")
347
+ commitSnippet();
348
+ }, placeholder: "e.g. Active users this week", className: "w-full rounded-md border border-border bg-background px-3 py-2 text-sm text-foreground outline-none focus:border-primary focus:ring-1 focus:ring-primary" })] }), _jsx("pre", { className: "max-h-40 overflow-auto rounded-md border border-border bg-muted/50 p-3 font-mono text-[11px] text-foreground", children: value.trim() }), _jsxs("div", { className: "flex justify-end gap-2", children: [_jsx("button", { type: "button", onClick: () => setSaveModalOpen(false), className: "inline-flex h-8 cursor-pointer items-center rounded-md border border-border bg-background px-3 text-xs font-medium text-foreground hover:bg-accent", children: "Cancel" }), _jsx("button", { type: "button", onClick: commitSnippet, disabled: !snippetName.trim(), className: "inline-flex h-8 cursor-pointer items-center rounded-md bg-primary px-3 text-xs font-semibold text-primary-foreground hover:bg-primary/90 disabled:cursor-not-allowed disabled:opacity-50", children: "Save" })] })] }) }))] }));
349
+ }
350
+ //# sourceMappingURL=SqlEditor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SqlEditor.js","sourceRoot":"","sources":["../../../src/client/db-admin/SqlEditor.tsx"],"names":[],"mappings":"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","sourcesContent":["/**\n * Production-grade SQL editor for the dev-mode database admin.\n *\n * Layout: a CodeMirror editor pane on top, a resizable results panel below.\n * Features schema-aware autocomplete, keyboard run shortcuts, history, named\n * snippets, CSV/JSON export, and a confirm modal for destructive statements.\n *\n * Data access goes through `runQuery` from `./useDbAdmin.js` (the shared\n * contract). On a destructive statement without confirmation, `runQuery` throws\n * an Error whose `needsConfirm` flag is `true`; we catch that and re-run after\n * the user confirms in a locally-built modal.\n */\nimport {\n useCallback,\n useEffect,\n useMemo,\n useRef,\n useState,\n type ReactNode,\n} from \"react\";\nimport CodeMirror, {\n type ReactCodeMirrorRef,\n EditorView,\n keymap,\n Prec,\n} from \"@uiw/react-codemirror\";\nimport { sql, PostgreSQL } from \"@codemirror/lang-sql\";\nimport { oneDark } from \"@codemirror/theme-one-dark\";\nimport {\n IconPlayerPlayFilled,\n IconHistory,\n IconBookmark,\n IconBookmarkPlus,\n IconDownload,\n IconAlertTriangle,\n IconLoader2,\n IconTrash,\n IconX,\n IconChevronDown,\n} from \"@tabler/icons-react\";\nimport { cn } from \"../utils.js\";\nimport {\n Popover,\n PopoverContent,\n PopoverTrigger,\n} from \"../components/ui/popover.js\";\nimport {\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuTrigger,\n} from \"../components/ui/dropdown-menu.js\";\nimport type { DbAdminDialect } from \"../../db-admin/types.js\";\nimport { runQuery } from \"./useDbAdmin.js\";\nimport { ResultsGrid } from \"./ResultsGrid.js\";\nimport { toCSV, toJSON, downloadFile } from \"./export-utils.js\";\nimport {\n loadHistory,\n pushHistory,\n clearHistory,\n loadSnippets,\n saveSnippet,\n deleteSnippet,\n type SqlSnippet,\n} from \"./sql-storage.js\";\n\nexport interface SqlEditorProps {\n dialect: DbAdminDialect;\n tableNames: string[];\n columnsByTable: Record<string, string[]>;\n}\n\ninterface QueryResult {\n columns: string[];\n rows: Record<string, unknown>[];\n rowsAffected: number;\n durationMs: number;\n}\n\n// ─── Dark-mode detection ─────────────────────────────────────────────────────\n\nfunction useIsDark(): boolean {\n const [isDark, setIsDark] = useState(false);\n useEffect(() => {\n if (typeof document === \"undefined\") return;\n const root = document.documentElement;\n const update = () => setIsDark(root.classList.contains(\"dark\"));\n update();\n const observer = new MutationObserver(update);\n observer.observe(root, { attributes: true, attributeFilter: [\"class\"] });\n return () => observer.disconnect();\n }, []);\n return isDark;\n}\n\n// ─── Statement-under-cursor helpers ──────────────────────────────────────────\n\n/**\n * Best-effort split of a SQL buffer into statements by top-level `;`, ignoring\n * semicolons inside single/double quotes or line/block comments. Returns each\n * statement with its character offsets so we can pick the one under the cursor.\n */\nfunction splitStatements(\n text: string,\n): { sql: string; start: number; end: number }[] {\n const out: { sql: string; start: number; end: number }[] = [];\n let start = 0;\n let inSingle = false;\n let inDouble = false;\n let inLineComment = false;\n let inBlockComment = false;\n\n for (let i = 0; i < text.length; i++) {\n const ch = text[i];\n const next = text[i + 1];\n\n if (inLineComment) {\n if (ch === \"\\n\") inLineComment = false;\n continue;\n }\n if (inBlockComment) {\n if (ch === \"*\" && next === \"/\") {\n inBlockComment = false;\n i++;\n }\n continue;\n }\n if (inSingle) {\n if (ch === \"'\") inSingle = false;\n continue;\n }\n if (inDouble) {\n if (ch === '\"') inDouble = false;\n continue;\n }\n\n if (ch === \"-\" && next === \"-\") {\n inLineComment = true;\n i++;\n continue;\n }\n if (ch === \"/\" && next === \"*\") {\n inBlockComment = true;\n i++;\n continue;\n }\n if (ch === \"'\") {\n inSingle = true;\n continue;\n }\n if (ch === '\"') {\n inDouble = true;\n continue;\n }\n if (ch === \";\") {\n out.push({ sql: text.slice(start, i + 1), start, end: i + 1 });\n start = i + 1;\n }\n }\n if (start < text.length) {\n out.push({ sql: text.slice(start), start, end: text.length });\n }\n return out;\n}\n\n/** Resolve which SQL to run given the current selection and cursor position. */\nfunction resolveRunTarget(\n view: EditorView | undefined,\n buffer: string,\n): string {\n if (!view) return buffer;\n const sel = view.state.selection.main;\n if (!sel.empty) {\n return view.state.sliceDoc(sel.from, sel.to);\n }\n const cursor = sel.head;\n const statements = splitStatements(buffer);\n const hit = statements.find((s) => cursor >= s.start && cursor <= s.end);\n return (hit?.sql ?? buffer).trim() || buffer;\n}\n\n// ─── Local modal primitive ───────────────────────────────────────────────────\n\nfunction Modal({\n title,\n children,\n onClose,\n}: {\n title: string;\n children: ReactNode;\n onClose: () => void;\n}) {\n useEffect(() => {\n const onKey = (e: KeyboardEvent) => {\n if (e.key === \"Escape\") onClose();\n };\n window.addEventListener(\"keydown\", onKey);\n return () => window.removeEventListener(\"keydown\", onKey);\n }, [onClose]);\n\n return (\n <div className=\"fixed inset-0 z-50 flex items-center justify-center p-4\">\n <div\n className=\"absolute inset-0 bg-black/50\"\n onClick={onClose}\n aria-hidden\n />\n <div className=\"relative z-10 w-full max-w-md rounded-lg border border-border bg-background shadow-xl\">\n <div className=\"flex items-center justify-between border-b border-border px-4 py-3\">\n <h2 className=\"text-sm font-semibold text-foreground\">{title}</h2>\n <button\n type=\"button\"\n onClick={onClose}\n className=\"cursor-pointer rounded p-1 text-muted-foreground hover:bg-accent hover:text-foreground\"\n aria-label=\"Close\"\n >\n <IconX size={16} />\n </button>\n </div>\n <div className=\"p-4\">{children}</div>\n </div>\n </div>\n );\n}\n\n// ─── Toolbar button ──────────────────────────────────────────────────────────\n\nfunction ToolbarButton({\n children,\n onClick,\n className,\n title,\n disabled,\n}: {\n children: ReactNode;\n onClick?: () => void;\n className?: string;\n title?: string;\n disabled?: boolean;\n}) {\n return (\n <button\n type=\"button\"\n onClick={onClick}\n title={title}\n disabled={disabled}\n className={cn(\n \"inline-flex h-8 cursor-pointer items-center gap-1.5 rounded-md border border-border bg-background px-2.5 text-xs font-medium text-foreground transition-colors hover:bg-accent disabled:cursor-not-allowed disabled:opacity-50\",\n className,\n )}\n >\n {children}\n </button>\n );\n}\n\nconst isMac =\n typeof navigator !== \"undefined\" &&\n /Mac|iPhone|iPad/.test(navigator.platform);\nconst MOD = isMac ? \"Cmd\" : \"Ctrl\";\n\n// ─── Main component ──────────────────────────────────────────────────────────\n\nexport function SqlEditor({\n dialect,\n tableNames,\n columnsByTable,\n}: SqlEditorProps) {\n const isDark = useIsDark();\n const editorRef = useRef<ReactCodeMirrorRef>(null);\n\n const [value, setValue] = useState(\"\");\n const [running, setRunning] = useState(false);\n const [result, setResult] = useState<QueryResult | null>(null);\n const [error, setError] = useState<string | null>(null);\n const [status, setStatus] = useState<string | null>(null);\n\n const [history, setHistory] = useState<string[]>([]);\n const [snippets, setSnippets] = useState<SqlSnippet[]>([]);\n\n const [confirmSql, setConfirmSql] = useState<string | null>(null);\n const [saveModalOpen, setSaveModalOpen] = useState(false);\n const [snippetName, setSnippetName] = useState(\"\");\n\n // Editor height (px) — draggable splitter between editor and results.\n const [editorHeight, setEditorHeight] = useState(240);\n const dragState = useRef<{ startY: number; startHeight: number } | null>(\n null,\n );\n\n // Keep the latest buffer accessible inside CodeMirror keymap closures.\n const valueRef = useRef(value);\n valueRef.current = value;\n\n useEffect(() => {\n setHistory(loadHistory());\n setSnippets(loadSnippets());\n }, []);\n\n // ─── Run logic ────────────────────────────────────────────────────────────\n\n const execute = useCallback(\n async (sqlText: string, confirmDestructive?: boolean) => {\n const trimmed = sqlText.trim();\n if (!trimmed) return;\n setRunning(true);\n setError(null);\n try {\n const res = await runQuery(trimmed, undefined, confirmDestructive);\n setResult({\n columns: res.columns,\n rows: res.rows,\n rowsAffected: res.rowsAffected,\n durationMs: res.durationMs,\n });\n if (res.columns.length > 0) {\n setStatus(`${res.rows.length} rows · ${res.durationMs}ms`);\n } else {\n setStatus(\n `Query OK · ${res.rowsAffected} rows affected · ${res.durationMs}ms`,\n );\n }\n setHistory(pushHistory(trimmed));\n } catch (err) {\n const e = err as Error & { needsConfirm?: boolean };\n if (e.needsConfirm) {\n setConfirmSql(trimmed);\n } else {\n setError(e.message || \"Query failed\");\n setStatus(null);\n }\n } finally {\n setRunning(false);\n }\n },\n [],\n );\n\n const runActiveStatement = useCallback(() => {\n const view = editorRef.current?.view;\n const target = resolveRunTarget(view, valueRef.current);\n void execute(target);\n }, [execute]);\n\n const runWholeBuffer = useCallback(() => {\n void execute(valueRef.current);\n }, [execute]);\n\n const confirmAndRun = useCallback(() => {\n const sqlText = confirmSql;\n setConfirmSql(null);\n if (sqlText) void execute(sqlText, true);\n }, [confirmSql, execute]);\n\n // ─── CodeMirror extensions ──────────────────────────────────────────────\n\n const extensions = useMemo(() => {\n const langExt = sql({\n dialect: dialect === \"postgres\" ? PostgreSQL : undefined,\n schema: columnsByTable,\n tables: tableNames.map((t) => ({ label: t })),\n upperCaseKeywords: true,\n });\n\n const runKeymap = Prec.highest(\n keymap.of([\n {\n key: \"Mod-Enter\",\n preventDefault: true,\n run: () => {\n runActiveStatement();\n return true;\n },\n },\n {\n key: \"Mod-Shift-Enter\",\n preventDefault: true,\n run: () => {\n runWholeBuffer();\n return true;\n },\n },\n ]),\n );\n\n return [runKeymap, langExt, EditorView.lineWrapping];\n // tableNames / columnsByTable identity is stable enough for our purposes;\n // re-derive when the dialect or schema reference changes.\n }, [dialect, columnsByTable, tableNames, runActiveStatement, runWholeBuffer]);\n\n // ─── Splitter drag ───────────────────────────────────────────────────────\n\n useEffect(() => {\n const onMove = (e: MouseEvent) => {\n if (!dragState.current) return;\n const delta = e.clientY - dragState.current.startY;\n const next = Math.min(\n Math.max(dragState.current.startHeight + delta, 120),\n 640,\n );\n setEditorHeight(next);\n };\n const onUp = () => {\n dragState.current = null;\n document.body.style.userSelect = \"\";\n };\n window.addEventListener(\"mousemove\", onMove);\n window.addEventListener(\"mouseup\", onUp);\n return () => {\n window.removeEventListener(\"mousemove\", onMove);\n window.removeEventListener(\"mouseup\", onUp);\n };\n }, []);\n\n const startDrag = (e: React.MouseEvent) => {\n dragState.current = { startY: e.clientY, startHeight: editorHeight };\n document.body.style.userSelect = \"none\";\n };\n\n // ─── Loading editor content from history / snippets ───────────────────────\n\n const loadIntoEditor = useCallback((sqlText: string) => {\n setValue(sqlText);\n // Focus and place cursor at end after the controlled value updates.\n requestAnimationFrame(() => {\n const view = editorRef.current?.view;\n if (view) {\n view.focus();\n view.dispatch({ selection: { anchor: view.state.doc.length } });\n }\n });\n }, []);\n\n // ─── Export ────────────────────────────────────────────────────────────────\n\n const exportAs = (format: \"csv\" | \"json\") => {\n if (!result || result.columns.length === 0) return;\n const stamp = new Date().toISOString().replace(/[:.]/g, \"-\").slice(0, 19);\n if (format === \"csv\") {\n downloadFile(\n `query-result-${stamp}.csv`,\n \"text/csv;charset=utf-8\",\n toCSV(result.columns, result.rows),\n );\n } else {\n downloadFile(\n `query-result-${stamp}.json`,\n \"application/json\",\n toJSON(result.rows),\n );\n }\n };\n\n // ─── Snippet save ────────────────────────────────────────────────────────\n\n const openSaveModal = () => {\n if (!value.trim()) return;\n setSnippetName(\"\");\n setSaveModalOpen(true);\n };\n\n const commitSnippet = () => {\n const name = snippetName.trim();\n if (!name || !value.trim()) return;\n setSnippets(saveSnippet({ name, sql: value }));\n setSaveModalOpen(false);\n };\n\n const removeSnippet = (id: string) => {\n setSnippets(deleteSnippet(id));\n };\n\n // ─── Example queries (empty state) ─────────────────────────────────────────\n\n const firstTable = tableNames[0];\n const examples = useMemo(() => {\n const list: { label: string; sql: string }[] = [];\n if (firstTable) {\n list.push({\n label: `Select rows from ${firstTable}`,\n sql: `SELECT * FROM ${firstTable} LIMIT 100;`,\n });\n }\n list.push({\n label: \"List tables\",\n sql:\n dialect === \"postgres\"\n ? \"SELECT table_name FROM information_schema.tables WHERE table_schema = 'public' ORDER BY table_name;\"\n : \"SELECT name FROM sqlite_master WHERE type = 'table' ORDER BY name;\",\n });\n if (firstTable) {\n list.push({\n label: `Count rows in ${firstTable}`,\n sql: `SELECT COUNT(*) AS total FROM ${firstTable};`,\n });\n }\n return list;\n }, [firstTable, dialect]);\n\n const hasResults = result !== null;\n const canExport = hasResults && result!.columns.length > 0;\n\n // ─── Render ──────────────────────────────────────────────────────────────\n\n return (\n <div className=\"flex h-full flex-col\">\n {/* Toolbar */}\n <div className=\"flex flex-wrap items-center gap-2 border-b border-border bg-background px-3 py-2\">\n <button\n type=\"button\"\n onClick={runActiveStatement}\n disabled={running || !value.trim()}\n title={`Run selection / statement (${MOD}+Enter)`}\n className=\"inline-flex h-8 cursor-pointer items-center gap-1.5 rounded-md bg-primary px-3 text-xs font-semibold text-primary-foreground transition-colors hover:bg-primary/90 disabled:cursor-not-allowed disabled:opacity-50\"\n >\n {running ? (\n <IconLoader2 size={14} className=\"animate-spin\" />\n ) : (\n <IconPlayerPlayFilled size={14} />\n )}\n Run\n </button>\n\n <span className=\"hidden text-[11px] text-muted-foreground sm:inline\">\n {MOD}+Enter runs selection / statement · {MOD}+Shift+Enter runs all\n </span>\n\n <div className=\"ml-auto flex items-center gap-2\">\n {/* History */}\n <Popover>\n <PopoverTrigger asChild>\n <ToolbarButton title=\"Query history\">\n <IconHistory size={14} />\n History\n </ToolbarButton>\n </PopoverTrigger>\n <PopoverContent align=\"end\" className=\"w-96 p-0\">\n <div className=\"flex items-center justify-between border-b border-border px-3 py-2\">\n <span className=\"text-xs font-semibold text-foreground\">\n Recent queries\n </span>\n {history.length > 0 && (\n <button\n type=\"button\"\n onClick={() => {\n clearHistory();\n setHistory([]);\n }}\n className=\"cursor-pointer text-[11px] text-muted-foreground hover:text-foreground\"\n >\n Clear\n </button>\n )}\n </div>\n <div className=\"max-h-80 overflow-auto py-1\">\n {history.length === 0 ? (\n <div className=\"px-3 py-6 text-center text-xs text-muted-foreground\">\n No queries yet\n </div>\n ) : (\n history.map((h, i) => (\n <button\n key={i}\n type=\"button\"\n onClick={() => loadIntoEditor(h)}\n className=\"block w-full cursor-pointer truncate px-3 py-1.5 text-left font-mono text-[11px] text-foreground hover:bg-accent\"\n title={h}\n >\n {h.replace(/\\s+/g, \" \").trim()}\n </button>\n ))\n )}\n </div>\n </PopoverContent>\n </Popover>\n\n {/* Snippets */}\n <Popover>\n <PopoverTrigger asChild>\n <ToolbarButton title=\"Saved snippets\">\n <IconBookmark size={14} />\n Snippets\n </ToolbarButton>\n </PopoverTrigger>\n <PopoverContent align=\"end\" className=\"w-96 p-0\">\n <div className=\"flex items-center justify-between border-b border-border px-3 py-2\">\n <span className=\"text-xs font-semibold text-foreground\">\n Saved snippets\n </span>\n <button\n type=\"button\"\n onClick={openSaveModal}\n disabled={!value.trim()}\n className=\"inline-flex cursor-pointer items-center gap-1 text-[11px] text-muted-foreground hover:text-foreground disabled:cursor-not-allowed disabled:opacity-50\"\n >\n <IconBookmarkPlus size={13} />\n Save current\n </button>\n </div>\n <div className=\"max-h-80 overflow-auto py-1\">\n {snippets.length === 0 ? (\n <div className=\"px-3 py-6 text-center text-xs text-muted-foreground\">\n No saved snippets\n </div>\n ) : (\n snippets.map((s) => (\n <div\n key={s.id}\n className=\"group flex items-center gap-2 px-3 py-1.5 hover:bg-accent\"\n >\n <button\n type=\"button\"\n onClick={() => loadIntoEditor(s.sql)}\n className=\"min-w-0 flex-1 cursor-pointer text-left\"\n title={s.sql}\n >\n <div className=\"truncate text-xs font-medium text-foreground\">\n {s.name}\n </div>\n <div className=\"truncate font-mono text-[10px] text-muted-foreground\">\n {s.sql.replace(/\\s+/g, \" \").trim()}\n </div>\n </button>\n <button\n type=\"button\"\n onClick={() => removeSnippet(s.id)}\n className=\"cursor-pointer rounded p-1 text-muted-foreground opacity-0 transition-opacity hover:text-red-500 group-hover:opacity-100\"\n title=\"Delete snippet\"\n >\n <IconTrash size={13} />\n </button>\n </div>\n ))\n )}\n </div>\n </PopoverContent>\n </Popover>\n\n {/* Export */}\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <ToolbarButton title=\"Export results\" disabled={!canExport}>\n <IconDownload size={14} />\n Export\n <IconChevronDown size={12} className=\"opacity-60\" />\n </ToolbarButton>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"end\">\n <DropdownMenuItem onClick={() => exportAs(\"csv\")}>\n Download CSV\n </DropdownMenuItem>\n <DropdownMenuItem onClick={() => exportAs(\"json\")}>\n Download JSON\n </DropdownMenuItem>\n </DropdownMenuContent>\n </DropdownMenu>\n </div>\n </div>\n\n {/* Editor pane */}\n <div\n className=\"shrink-0 overflow-hidden border-b border-border\"\n style={{ height: editorHeight }}\n >\n <CodeMirror\n ref={editorRef}\n value={value}\n onChange={setValue}\n height={`${editorHeight}px`}\n theme={isDark ? oneDark : undefined}\n extensions={extensions}\n placeholder=\"Write SQL here…\"\n basicSetup={{\n lineNumbers: true,\n highlightActiveLine: true,\n autocompletion: true,\n bracketMatching: true,\n closeBrackets: true,\n }}\n style={{ fontSize: 13, height: \"100%\" }}\n />\n </div>\n\n {/* Splitter */}\n <div\n onMouseDown={startDrag}\n className=\"h-1.5 shrink-0 cursor-row-resize bg-border/40 transition-colors hover:bg-primary/40\"\n role=\"separator\"\n aria-orientation=\"horizontal\"\n />\n\n {/* Status / error bar */}\n {(status || error) && (\n <div className=\"shrink-0 px-3 py-1.5\">\n {error ? (\n <div className=\"flex items-start gap-2 rounded-md border border-red-500/40 bg-red-500/10 px-3 py-2 text-xs text-red-500\">\n <IconAlertTriangle size={14} className=\"mt-px shrink-0\" />\n <span className=\"break-words font-mono\">{error}</span>\n </div>\n ) : (\n <div className=\"font-mono text-[11px] text-muted-foreground\">\n {status}\n </div>\n )}\n </div>\n )}\n\n {/* Results panel */}\n <div className=\"min-h-0 flex-1 overflow-hidden\">\n {hasResults ? (\n <ResultsGrid columns={result!.columns} rows={result!.rows} />\n ) : (\n <div className=\"flex h-full flex-col items-center justify-center gap-4 p-6 text-center\">\n <div className=\"text-sm text-muted-foreground\">\n Run a query to see results, or start with an example:\n </div>\n <div className=\"flex flex-col gap-2\">\n {examples.map((ex, i) => (\n <button\n key={i}\n type=\"button\"\n onClick={() => loadIntoEditor(ex.sql)}\n className=\"cursor-pointer rounded-md border border-border bg-background px-4 py-2 text-left transition-colors hover:bg-accent\"\n >\n <div className=\"text-xs font-medium text-foreground\">\n {ex.label}\n </div>\n <div className=\"mt-0.5 font-mono text-[11px] text-muted-foreground\">\n {ex.sql}\n </div>\n </button>\n ))}\n </div>\n </div>\n )}\n </div>\n\n {/* Destructive-confirm modal */}\n {confirmSql && (\n <Modal\n title=\"Confirm destructive query\"\n onClose={() => setConfirmSql(null)}\n >\n <div className=\"space-y-4\">\n <div className=\"flex items-start gap-2 text-sm text-foreground\">\n <IconAlertTriangle\n size={18}\n className=\"mt-px shrink-0 text-yellow-500\"\n />\n <p>\n This looks like a destructive query (DROP / TRUNCATE / DELETE\n without WHERE). Run it?\n </p>\n </div>\n <pre className=\"max-h-40 overflow-auto rounded-md border border-border bg-muted/50 p-3 font-mono text-[11px] text-foreground\">\n {confirmSql}\n </pre>\n <div className=\"flex justify-end gap-2\">\n <button\n type=\"button\"\n onClick={() => setConfirmSql(null)}\n className=\"inline-flex h-8 cursor-pointer items-center rounded-md border border-border bg-background px-3 text-xs font-medium text-foreground hover:bg-accent\"\n >\n Cancel\n </button>\n <button\n type=\"button\"\n onClick={confirmAndRun}\n className=\"inline-flex h-8 cursor-pointer items-center rounded-md bg-red-500 px-3 text-xs font-semibold text-white hover:bg-red-600\"\n >\n Run anyway\n </button>\n </div>\n </div>\n </Modal>\n )}\n\n {/* Save-snippet modal */}\n {saveModalOpen && (\n <Modal title=\"Save snippet\" onClose={() => setSaveModalOpen(false)}>\n <div className=\"space-y-4\">\n <div>\n <label className=\"mb-1.5 block text-xs font-medium text-muted-foreground\">\n Snippet name\n </label>\n <input\n autoFocus\n value={snippetName}\n onChange={(e) => setSnippetName(e.target.value)}\n onKeyDown={(e) => {\n if (e.key === \"Enter\") commitSnippet();\n }}\n placeholder=\"e.g. Active users this week\"\n className=\"w-full rounded-md border border-border bg-background px-3 py-2 text-sm text-foreground outline-none focus:border-primary focus:ring-1 focus:ring-primary\"\n />\n </div>\n <pre className=\"max-h-40 overflow-auto rounded-md border border-border bg-muted/50 p-3 font-mono text-[11px] text-foreground\">\n {value.trim()}\n </pre>\n <div className=\"flex justify-end gap-2\">\n <button\n type=\"button\"\n onClick={() => setSaveModalOpen(false)}\n className=\"inline-flex h-8 cursor-pointer items-center rounded-md border border-border bg-background px-3 text-xs font-medium text-foreground hover:bg-accent\"\n >\n Cancel\n </button>\n <button\n type=\"button\"\n onClick={commitSnippet}\n disabled={!snippetName.trim()}\n className=\"inline-flex h-8 cursor-pointer items-center rounded-md bg-primary px-3 text-xs font-semibold text-primary-foreground hover:bg-primary/90 disabled:cursor-not-allowed disabled:opacity-50\"\n >\n Save\n </button>\n </div>\n </div>\n </Modal>\n )}\n </div>\n );\n}\n"]}
@@ -0,0 +1,10 @@
1
+ import type { DbAdminTableSummary } from "../../db-admin/types.js";
2
+ export interface TableBrowserProps {
3
+ tables: DbAdminTableSummary[];
4
+ selected: string | null;
5
+ onSelect: (table: string) => void;
6
+ mode: "table" | "sql";
7
+ onModeChange: (mode: "table" | "sql") => void;
8
+ }
9
+ export declare function TableBrowser({ tables, selected, onSelect, mode, onModeChange, }: TableBrowserProps): import("react/jsx-runtime").JSX.Element;
10
+ //# sourceMappingURL=TableBrowser.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TableBrowser.d.ts","sourceRoot":"","sources":["../../../src/client/db-admin/TableBrowser.tsx"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAEnE,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,mBAAmB,EAAE,CAAC;IAC9B,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,IAAI,EAAE,OAAO,GAAG,KAAK,CAAC;IACtB,YAAY,EAAE,CAAC,IAAI,EAAE,OAAO,GAAG,KAAK,KAAK,IAAI,CAAC;CAC/C;AAmBD,wBAAgB,YAAY,CAAC,EAC3B,MAAM,EACN,QAAQ,EACR,QAAQ,EACR,IAAI,EACJ,YAAY,GACb,EAAE,iBAAiB,2CAsHnB"}
@@ -0,0 +1,61 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /**
3
+ * Left sidebar for the database admin: a mode toggle (Table Editor / SQL
4
+ * Editor), a debounced search box, and a scrollable list of tables and views
5
+ * with row counts.
6
+ */
7
+ import { useEffect, useMemo, useRef, useState } from "react";
8
+ import { IconTable, IconEye, IconSearch, IconDatabaseOff, IconX, } from "@tabler/icons-react";
9
+ import { cn } from "../utils.js";
10
+ function formatCount(count) {
11
+ if (count === null)
12
+ return "";
13
+ if (count < 1000)
14
+ return String(count);
15
+ if (count < 1_000_000)
16
+ return `${(count / 1000).toFixed(count < 10_000 ? 1 : 0)}k`;
17
+ return `${(count / 1_000_000).toFixed(1)}M`;
18
+ }
19
+ function useDebounced(value, delay = 150) {
20
+ const [debounced, setDebounced] = useState(value);
21
+ useEffect(() => {
22
+ const id = setTimeout(() => setDebounced(value), delay);
23
+ return () => clearTimeout(id);
24
+ }, [value, delay]);
25
+ return debounced;
26
+ }
27
+ export function TableBrowser({ tables, selected, onSelect, mode, onModeChange, }) {
28
+ const [search, setSearch] = useState("");
29
+ const debouncedSearch = useDebounced(search);
30
+ const inputRef = useRef(null);
31
+ const filtered = useMemo(() => {
32
+ const q = debouncedSearch.trim().toLowerCase();
33
+ const list = q
34
+ ? tables.filter((t) => t.name.toLowerCase().includes(q))
35
+ : tables;
36
+ return [...list].sort((a, b) => a.name.localeCompare(b.name));
37
+ }, [tables, debouncedSearch]);
38
+ return (_jsxs("div", { className: "flex h-full w-full flex-col bg-card", children: [_jsx("div", { className: "border-b p-2", children: _jsxs("div", { className: "grid grid-cols-2 gap-1 rounded-md bg-muted p-1", children: [_jsx(ModeButton, { active: mode === "table", onClick: () => onModeChange("table"), children: "Table Editor" }), _jsx(ModeButton, { active: mode === "sql", onClick: () => onModeChange("sql"), children: "SQL Editor" })] }) }), _jsx("div", { className: "border-b p-2", children: _jsxs("div", { className: "relative", children: [_jsx(IconSearch, { className: "pointer-events-none absolute left-2.5 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground", stroke: 1.75 }), _jsx("input", { ref: inputRef, value: search, onChange: (e) => setSearch(e.target.value), placeholder: "Search tables\u2026", spellCheck: false, className: cn("h-9 w-full rounded-md border border-input bg-background pl-8 pr-8 text-sm", "ring-offset-background placeholder:text-muted-foreground", "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1") }), search && (_jsx("button", { type: "button", "aria-label": "Clear search", onClick: () => {
39
+ setSearch("");
40
+ inputRef.current?.focus();
41
+ }, className: "absolute right-1.5 top-1/2 -translate-y-1/2 cursor-pointer rounded p-1 text-muted-foreground hover:bg-muted hover:text-foreground", children: _jsx(IconX, { className: "h-3.5 w-3.5" }) }))] }) }), _jsx("div", { className: "min-h-0 flex-1 overflow-y-auto p-1.5", children: filtered.length === 0 ? (_jsx(EmptyState, { hasSearch: !!debouncedSearch.trim() })) : (_jsx("ul", { className: "space-y-0.5", children: filtered.map((t) => {
42
+ const isSelected = t.name === selected;
43
+ const Icon = t.type === "view" ? IconEye : IconTable;
44
+ return (_jsx("li", { children: _jsxs("button", { type: "button", onClick: () => onSelect(t.name), "aria-current": isSelected ? "true" : undefined, title: t.name, className: cn("group flex w-full cursor-pointer items-center gap-2 rounded-md px-2 py-1.5 text-left text-sm", "transition-colors", isSelected
45
+ ? "bg-accent text-accent-foreground"
46
+ : "text-foreground hover:bg-muted"), children: [_jsx(Icon, { className: cn("h-4 w-4 shrink-0", isSelected
47
+ ? "text-accent-foreground"
48
+ : "text-muted-foreground"), stroke: 1.75 }), _jsx("span", { className: "min-w-0 flex-1 truncate font-medium", children: t.name }), t.rowCount !== null && (_jsx("span", { className: "shrink-0 text-xs tabular-nums text-muted-foreground", children: formatCount(t.rowCount) }))] }) }, t.name));
49
+ }) })) })] }));
50
+ }
51
+ function ModeButton({ active, onClick, children, }) {
52
+ return (_jsx("button", { type: "button", onClick: onClick, "aria-pressed": active, className: cn("cursor-pointer rounded-sm px-2.5 py-1.5 text-xs font-medium transition-colors", active
53
+ ? "bg-background text-foreground shadow-sm"
54
+ : "text-muted-foreground hover:text-foreground"), children: children }));
55
+ }
56
+ function EmptyState({ hasSearch }) {
57
+ return (_jsxs("div", { className: "flex flex-col items-center justify-center px-4 py-10 text-center", children: [_jsx(IconDatabaseOff, { className: "mb-2 h-6 w-6 text-muted-foreground/60", stroke: 1.5 }), _jsx("p", { className: "text-sm font-medium text-foreground", children: hasSearch ? "No matches" : "No tables yet" }), _jsx("p", { className: "mt-0.5 text-xs text-muted-foreground", children: hasSearch
58
+ ? "Try a different search term."
59
+ : "Tables will appear here once created." })] }));
60
+ }
61
+ //# sourceMappingURL=TableBrowser.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TableBrowser.js","sourceRoot":"","sources":["../../../src/client/db-admin/TableBrowser.tsx"],"names":[],"mappings":"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","sourcesContent":["/**\n * Left sidebar for the database admin: a mode toggle (Table Editor / SQL\n * Editor), a debounced search box, and a scrollable list of tables and views\n * with row counts.\n */\nimport { useEffect, useMemo, useRef, useState } from \"react\";\nimport {\n IconTable,\n IconEye,\n IconSearch,\n IconDatabaseOff,\n IconX,\n} from \"@tabler/icons-react\";\nimport { cn } from \"../utils.js\";\nimport type { DbAdminTableSummary } from \"../../db-admin/types.js\";\n\nexport interface TableBrowserProps {\n tables: DbAdminTableSummary[];\n selected: string | null;\n onSelect: (table: string) => void;\n mode: \"table\" | \"sql\";\n onModeChange: (mode: \"table\" | \"sql\") => void;\n}\n\nfunction formatCount(count: number | null): string {\n if (count === null) return \"\";\n if (count < 1000) return String(count);\n if (count < 1_000_000)\n return `${(count / 1000).toFixed(count < 10_000 ? 1 : 0)}k`;\n return `${(count / 1_000_000).toFixed(1)}M`;\n}\n\nfunction useDebounced(value: string, delay = 150): string {\n const [debounced, setDebounced] = useState(value);\n useEffect(() => {\n const id = setTimeout(() => setDebounced(value), delay);\n return () => clearTimeout(id);\n }, [value, delay]);\n return debounced;\n}\n\nexport function TableBrowser({\n tables,\n selected,\n onSelect,\n mode,\n onModeChange,\n}: TableBrowserProps) {\n const [search, setSearch] = useState(\"\");\n const debouncedSearch = useDebounced(search);\n const inputRef = useRef<HTMLInputElement>(null);\n\n const filtered = useMemo(() => {\n const q = debouncedSearch.trim().toLowerCase();\n const list = q\n ? tables.filter((t) => t.name.toLowerCase().includes(q))\n : tables;\n return [...list].sort((a, b) => a.name.localeCompare(b.name));\n }, [tables, debouncedSearch]);\n\n return (\n <div className=\"flex h-full w-full flex-col bg-card\">\n {/* Mode toggle */}\n <div className=\"border-b p-2\">\n <div className=\"grid grid-cols-2 gap-1 rounded-md bg-muted p-1\">\n <ModeButton\n active={mode === \"table\"}\n onClick={() => onModeChange(\"table\")}\n >\n Table Editor\n </ModeButton>\n <ModeButton\n active={mode === \"sql\"}\n onClick={() => onModeChange(\"sql\")}\n >\n SQL Editor\n </ModeButton>\n </div>\n </div>\n\n {/* Search */}\n <div className=\"border-b p-2\">\n <div className=\"relative\">\n <IconSearch\n className=\"pointer-events-none absolute left-2.5 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground\"\n stroke={1.75}\n />\n <input\n ref={inputRef}\n value={search}\n onChange={(e) => setSearch(e.target.value)}\n placeholder=\"Search tables…\"\n spellCheck={false}\n className={cn(\n \"h-9 w-full rounded-md border border-input bg-background pl-8 pr-8 text-sm\",\n \"ring-offset-background placeholder:text-muted-foreground\",\n \"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1\",\n )}\n />\n {search && (\n <button\n type=\"button\"\n aria-label=\"Clear search\"\n onClick={() => {\n setSearch(\"\");\n inputRef.current?.focus();\n }}\n className=\"absolute right-1.5 top-1/2 -translate-y-1/2 cursor-pointer rounded p-1 text-muted-foreground hover:bg-muted hover:text-foreground\"\n >\n <IconX className=\"h-3.5 w-3.5\" />\n </button>\n )}\n </div>\n </div>\n\n {/* Table list */}\n <div className=\"min-h-0 flex-1 overflow-y-auto p-1.5\">\n {filtered.length === 0 ? (\n <EmptyState hasSearch={!!debouncedSearch.trim()} />\n ) : (\n <ul className=\"space-y-0.5\">\n {filtered.map((t) => {\n const isSelected = t.name === selected;\n const Icon = t.type === \"view\" ? IconEye : IconTable;\n return (\n <li key={t.name}>\n <button\n type=\"button\"\n onClick={() => onSelect(t.name)}\n aria-current={isSelected ? \"true\" : undefined}\n title={t.name}\n className={cn(\n \"group flex w-full cursor-pointer items-center gap-2 rounded-md px-2 py-1.5 text-left text-sm\",\n \"transition-colors\",\n isSelected\n ? \"bg-accent text-accent-foreground\"\n : \"text-foreground hover:bg-muted\",\n )}\n >\n <Icon\n className={cn(\n \"h-4 w-4 shrink-0\",\n isSelected\n ? \"text-accent-foreground\"\n : \"text-muted-foreground\",\n )}\n stroke={1.75}\n />\n <span className=\"min-w-0 flex-1 truncate font-medium\">\n {t.name}\n </span>\n {t.rowCount !== null && (\n <span className=\"shrink-0 text-xs tabular-nums text-muted-foreground\">\n {formatCount(t.rowCount)}\n </span>\n )}\n </button>\n </li>\n );\n })}\n </ul>\n )}\n </div>\n </div>\n );\n}\n\nfunction ModeButton({\n active,\n onClick,\n children,\n}: {\n active: boolean;\n onClick: () => void;\n children: React.ReactNode;\n}) {\n return (\n <button\n type=\"button\"\n onClick={onClick}\n aria-pressed={active}\n className={cn(\n \"cursor-pointer rounded-sm px-2.5 py-1.5 text-xs font-medium transition-colors\",\n active\n ? \"bg-background text-foreground shadow-sm\"\n : \"text-muted-foreground hover:text-foreground\",\n )}\n >\n {children}\n </button>\n );\n}\n\nfunction EmptyState({ hasSearch }: { hasSearch: boolean }) {\n return (\n <div className=\"flex flex-col items-center justify-center px-4 py-10 text-center\">\n <IconDatabaseOff\n className=\"mb-2 h-6 w-6 text-muted-foreground/60\"\n stroke={1.5}\n />\n <p className=\"text-sm font-medium text-foreground\">\n {hasSearch ? \"No matches\" : \"No tables yet\"}\n </p>\n <p className=\"mt-0.5 text-xs text-muted-foreground\">\n {hasSearch\n ? \"Try a different search term.\"\n : \"Tables will appear here once created.\"}\n </p>\n </div>\n );\n}\n"]}
@@ -0,0 +1,9 @@
1
+ import type { DbAdminDialect, DbAdminFilter } from "../../db-admin/types.js";
2
+ export interface TableEditorProps {
3
+ table: string;
4
+ dialect: DbAdminDialect;
5
+ initialFilters?: DbAdminFilter[];
6
+ onNavigateToRow: (table: string, filters: DbAdminFilter[]) => void;
7
+ }
8
+ export declare function TableEditor({ table, dialect, initialFilters, onNavigateToRow, }: TableEditorProps): import("react/jsx-runtime").JSX.Element;
9
+ //# sourceMappingURL=TableEditor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TableEditor.d.ts","sourceRoot":"","sources":["../../../src/client/db-admin/TableEditor.tsx"],"names":[],"mappings":"AA+BA,OAAO,KAAK,EACV,cAAc,EACd,aAAa,EAId,MAAM,yBAAyB,CAAC;AAEjC,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,cAAc,CAAC;IACxB,cAAc,CAAC,EAAE,aAAa,EAAE,CAAC;IACjC,eAAe,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,aAAa,EAAE,KAAK,IAAI,CAAC;CACpE;AAKD,wBAAgB,WAAW,CAAC,EAC1B,KAAK,EACL,OAAO,EACP,cAAc,EACd,eAAe,GAChB,EAAE,gBAAgB,2CAielB"}