@lunora/studio 0.0.0 → 1.0.0-alpha.2

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 (81) hide show
  1. package/LICENSE.md +105 -0
  2. package/README.md +123 -9
  3. package/__assets__/package-og.svg +14 -0
  4. package/dist/index.d.ts +1402 -0
  5. package/dist/index.js +41 -0
  6. package/dist/mount.d.ts +21 -0
  7. package/dist/mount.js +26 -0
  8. package/dist/packem_shared/ADMIN_FUNCTION_PREFIX-DmBqMZ-z.js +45 -0
  9. package/dist/packem_shared/ApiDocsPanel-DpRjJhG5.js +842 -0
  10. package/dist/packem_shared/ApiReferencePanel-DMIUp-kK.js +229 -0
  11. package/dist/packem_shared/ApiTab-DURGU15e.js +251 -0
  12. package/dist/packem_shared/AuditPanel-BC59Nhst.js +212 -0
  13. package/dist/packem_shared/CommandPalette-Dx_CoB9i.js +373 -0
  14. package/dist/packem_shared/ConfirmButton-WQVUoGFb.js +59 -0
  15. package/dist/packem_shared/ConnectionBadge-Bxagrip8.js +111 -0
  16. package/dist/packem_shared/DEFAULT_AUTO_REFRESH_MS-Vxwaxx51.js +50 -0
  17. package/dist/packem_shared/DEFAULT_INSIGHT_THRESHOLDS-DjF0h-gA.js +89 -0
  18. package/dist/packem_shared/DataBrowser-Coz6jJE6.js +4542 -0
  19. package/dist/packem_shared/DataFilters-FNquMaiu.js +249 -0
  20. package/dist/packem_shared/ErrorBoundary-BzAApI7J.js +66 -0
  21. package/dist/packem_shared/ExportImportPanel-WO34fJxy.js +193 -0
  22. package/dist/packem_shared/FileBrowser-Zcr-Qgxo.js +2932 -0
  23. package/dist/packem_shared/FunctionRunner-j0Rd5m9t.js +343 -0
  24. package/dist/packem_shared/FunctionStatsPanel-DboBl-XL.js +432 -0
  25. package/dist/packem_shared/GlobalDataBrowser-9MhPEfgN.js +318 -0
  26. package/dist/packem_shared/HealthPanel-DOIgbUtx.js +640 -0
  27. package/dist/packem_shared/HomePanel-bdOCNA-p.js +1273 -0
  28. package/dist/packem_shared/InsightsPanel-DaZPnSgt.js +423 -0
  29. package/dist/packem_shared/LogsPanel-CWdqAGpQ.js +839 -0
  30. package/dist/packem_shared/MailPanel-D_EGtDnS.js +447 -0
  31. package/dist/packem_shared/MetricsPanel-E4Gv6wTO.js +1625 -0
  32. package/dist/packem_shared/MigrationsPanel-DQdPY9io.js +246 -0
  33. package/dist/packem_shared/OpenRpcReferencePanel-j2p3HB0s.js +191 -0
  34. package/dist/packem_shared/PitrPanel-BbBkQR6t.js +252 -0
  35. package/dist/packem_shared/STUDIO_ROOT_CLASS-D12gX2dV.js +3 -0
  36. package/dist/packem_shared/ScheduledJobs-Ok1CYYwI.js +159 -0
  37. package/dist/packem_shared/SchemaViewer-D8XGnp-X.js +2512 -0
  38. package/dist/packem_shared/SecurityAdvisorPanel-Cdm2IxLW.js +79 -0
  39. package/dist/packem_shared/SettingsPanel-D3WF2mBU.js +176 -0
  40. package/dist/packem_shared/ShardInput-DNCsT1KW.js +107 -0
  41. package/dist/packem_shared/SqlEditorPanel-BuQ7f2Hs.js +13 -0
  42. package/dist/packem_shared/Studio-D36od9Oz.js +33 -0
  43. package/dist/packem_shared/StudioApp-dvywkJ8I.js +383 -0
  44. package/dist/packem_shared/StudioI18nProvider-Dcajsznk.js +48 -0
  45. package/dist/packem_shared/TableEditor-DIVDk3vT.js +371 -0
  46. package/dist/packem_shared/advisor-view-DBlzJi6C.js +159 -0
  47. package/dist/packem_shared/aggregateMetrics-D4nUHEKU.js +108 -0
  48. package/dist/packem_shared/app.d-CCmwDEVs.d.ts +300 -0
  49. package/dist/packem_shared/badge-B2PKA1-5.js +49 -0
  50. package/dist/packem_shared/bar-chart-CzJAgqkp.js +3245 -0
  51. package/dist/packem_shared/button-BhsN2uZH.js +49 -0
  52. package/dist/packem_shared/card-DURq3ElK.js +175 -0
  53. package/dist/packem_shared/cf-links-BZfRdxSE.js +8 -0
  54. package/dist/packem_shared/checkbox-UNkzAxl-.js +63 -0
  55. package/dist/packem_shared/createStudioI18n-CgvlmDkN.js +27 -0
  56. package/dist/packem_shared/data-grid-CCh2Couo.js +183 -0
  57. package/dist/packem_shared/dropdown-menu-WY4B_eJO.js +280 -0
  58. package/dist/packem_shared/empty-state-DY_oe0k6.js +98 -0
  59. package/dist/packem_shared/grid-features-DTjG6Sex.js +840 -0
  60. package/dist/packem_shared/input-XH4r1Pt1.js +53 -0
  61. package/dist/packem_shared/internal-BBZYexre.js +68 -0
  62. package/dist/packem_shared/label-D8ykjn5J.js +46 -0
  63. package/dist/packem_shared/live-status-bPff1O7Y.js +44 -0
  64. package/dist/packem_shared/reference-view-BCKIoai7.js +2180 -0
  65. package/dist/packem_shared/shard-history-DyebH1R5.js +38 -0
  66. package/dist/packem_shared/sparkline-10dG-_f0.js +93 -0
  67. package/dist/packem_shared/sql-editor-panel-CW2y2x9h.js +2562 -0
  68. package/dist/packem_shared/storage-tier-CL98eOvn.js +85 -0
  69. package/dist/packem_shared/studio-BDVd7rIV.js +10303 -0
  70. package/dist/packem_shared/table-_RzNvy3R.js +246 -0
  71. package/dist/packem_shared/table-list-sidebar-aZHLq70w.js +832 -0
  72. package/dist/packem_shared/textarea-D3gaCU_-.js +46 -0
  73. package/dist/packem_shared/use-live-admin-D1h1Fzsd.js +73 -0
  74. package/dist/packem_shared/use-live-shard-seed-B74RYcOy.js +76 -0
  75. package/dist/packem_shared/useDebounced-Dxncpg6z.js +32 -0
  76. package/dist/packem_shared/utils-B05Dmz_H.js +8 -0
  77. package/dist/packem_shared/virtual-rect-CVMUskSm.js +10 -0
  78. package/dist/standalone/studio.js +356 -0
  79. package/dist/styles.css +2 -0
  80. package/package.json +77 -17
  81. package/src/theme.css +59 -0
@@ -0,0 +1,2562 @@
1
+ import { c } from 'react/compiler-runtime';
2
+ import { useLunora } from '@lunora/react';
3
+ import { useState, useEffect, useRef, useId } from 'react';
4
+ import { useT } from './createStudioI18n-CgvlmDkN.js';
5
+ import { h as formatCell, f as fireAndForget, c as callOptions, a as adminRef, e as errorMessage } from './internal-BBZYexre.js';
6
+ import { X as XAxis, B as Bar, T as Tooltip, E as EvilBarChart } from './bar-chart-CzJAgqkp.js';
7
+ import { jsxDEV, Fragment } from 'react/jsx-dev-runtime';
8
+ import { ShardInput } from './ShardInput-DNCsT1KW.js';
9
+ import { cva } from 'class-variance-authority';
10
+ import { c as cn } from './utils-B05Dmz_H.js';
11
+ import { c as TableHead, a as TableHeader, b as TableRow, e as TableCell, d as TableBody, T as Table } from './table-_RzNvy3R.js';
12
+ import { ADMIN_FUNCTIONS } from './ADMIN_FUNCTION_PREFIX-DmBqMZ-z.js';
13
+ import { r as recordShard } from './shard-history-DyebH1R5.js';
14
+ import { C as CellValue } from './data-grid-CCh2Couo.js';
15
+ import { E as ExportMenu } from './grid-features-DTjG6Sex.js';
16
+
17
+ const MAX_BARS = 50;
18
+ const RESULT_CHART_CONFIG = {
19
+ value: {
20
+ colors: {
21
+ dark: ["var(--chart-1)"],
22
+ light: ["var(--chart-1)"]
23
+ },
24
+ label: "value"
25
+ }
26
+ };
27
+ const pickColumns = (result) => {
28
+ const value = result.columns.find((column) => result.rows.length > 0 && result.rows.every((row) => typeof row[column] === "number")) ?? null;
29
+ const label = result.columns.find((column) => column !== value) ?? null;
30
+ return {
31
+ label,
32
+ value
33
+ };
34
+ };
35
+ const SqlResultChart = (t0) => {
36
+ const $ = c(15);
37
+ const {
38
+ result
39
+ } = t0;
40
+ const t = useT();
41
+ let t1;
42
+ if ($[0] !== result) {
43
+ t1 = pickColumns(result);
44
+ $[0] = result;
45
+ $[1] = t1;
46
+ } else {
47
+ t1 = $[1];
48
+ }
49
+ const {
50
+ label,
51
+ value
52
+ } = t1;
53
+ let t2;
54
+ if ($[2] !== label || $[3] !== result || $[4] !== value) {
55
+ t2 = value === null ? [] : result.rows.slice(0, MAX_BARS).map((row, index) => ({
56
+ label: label === null ? `#${(index + 1).toString()}` : formatCell(row[label]),
57
+ value: Number(row[value]) || 0
58
+ }));
59
+ $[2] = label;
60
+ $[3] = result;
61
+ $[4] = value;
62
+ $[5] = t2;
63
+ } else {
64
+ t2 = $[5];
65
+ }
66
+ const data = t2;
67
+ if (value === null) {
68
+ let t32;
69
+ if ($[6] !== t) {
70
+ t32 = t("No numeric column to chart.");
71
+ $[6] = t;
72
+ $[7] = t32;
73
+ } else {
74
+ t32 = $[7];
75
+ }
76
+ let t42;
77
+ if ($[8] !== t32) {
78
+ t42 = /* @__PURE__ */ jsxDEV("p", {
79
+ className: "p-4 text-sm text-muted-foreground",
80
+ "data-testid": "sql-chart-empty",
81
+ children: t32
82
+ }, void 0, false);
83
+ $[8] = t32;
84
+ $[9] = t42;
85
+ } else {
86
+ t42 = $[9];
87
+ }
88
+ return t42;
89
+ }
90
+ let t3;
91
+ let t4;
92
+ let t5;
93
+ if ($[10] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) {
94
+ t3 = /* @__PURE__ */ jsxDEV(XAxis, {
95
+ dataKey: "label"
96
+ }, void 0, false);
97
+ t4 = /* @__PURE__ */ jsxDEV(Bar, {
98
+ dataKey: "value"
99
+ }, void 0, false);
100
+ t5 = /* @__PURE__ */ jsxDEV(Tooltip, {}, void 0, false);
101
+ $[10] = t3;
102
+ $[11] = t4;
103
+ $[12] = t5;
104
+ } else {
105
+ t3 = $[10];
106
+ t4 = $[11];
107
+ t5 = $[12];
108
+ }
109
+ let t6;
110
+ if ($[13] !== data) {
111
+ t6 = /* @__PURE__ */ jsxDEV("div", {
112
+ className: "h-80 w-full p-3",
113
+ "data-testid": "sql-chart",
114
+ children: /* @__PURE__ */ jsxDEV(EvilBarChart, {
115
+ className: "h-full w-full",
116
+ config: RESULT_CHART_CONFIG,
117
+ data,
118
+ children: [t3, t4, t5]
119
+ }, void 0, true)
120
+ }, void 0, false);
121
+ $[13] = data;
122
+ $[14] = t6;
123
+ } else {
124
+ t6 = $[14];
125
+ }
126
+ return t6;
127
+ };
128
+
129
+ const alertVariants = cva("flex items-start gap-2 rounded-md border px-3 py-2 text-sm [&>svg]:mt-0.5 [&>svg]:size-4 [&>svg]:shrink-0", {
130
+ defaultVariants: {
131
+ variant: "default"
132
+ },
133
+ variants: {
134
+ variant: {
135
+ default: "border-border bg-muted/40 text-foreground",
136
+ destructive: "border-destructive/40 bg-destructive/5 text-destructive",
137
+ warning: "border-amber-500/40 bg-amber-500/5 text-amber-700 dark:text-amber-400"
138
+ }
139
+ }
140
+ });
141
+ const Alert = (t0) => {
142
+ const $ = c(10);
143
+ const {
144
+ children,
145
+ className,
146
+ icon,
147
+ testId,
148
+ variant
149
+ } = t0;
150
+ let t1;
151
+ if ($[0] !== className || $[1] !== variant) {
152
+ t1 = cn(alertVariants({
153
+ variant
154
+ }), className);
155
+ $[0] = className;
156
+ $[1] = variant;
157
+ $[2] = t1;
158
+ } else {
159
+ t1 = $[2];
160
+ }
161
+ let t2;
162
+ if ($[3] !== children) {
163
+ t2 = /* @__PURE__ */ jsxDEV("div", {
164
+ className: "min-w-0 flex-1",
165
+ children
166
+ }, void 0, false);
167
+ $[3] = children;
168
+ $[4] = t2;
169
+ } else {
170
+ t2 = $[4];
171
+ }
172
+ let t3;
173
+ if ($[5] !== icon || $[6] !== t1 || $[7] !== t2 || $[8] !== testId) {
174
+ t3 = /* @__PURE__ */ jsxDEV("div", {
175
+ className: t1,
176
+ "data-testid": testId,
177
+ role: "alert",
178
+ children: [icon, t2]
179
+ }, void 0, true);
180
+ $[5] = icon;
181
+ $[6] = t1;
182
+ $[7] = t2;
183
+ $[8] = testId;
184
+ $[9] = t3;
185
+ } else {
186
+ t3 = $[9];
187
+ }
188
+ return t3;
189
+ };
190
+
191
+ const store = () => {
192
+ try {
193
+ return globalThis.localStorage;
194
+ } catch {
195
+ return void 0;
196
+ }
197
+ };
198
+ const loadJsonArray = function(key) {
199
+ try {
200
+ const raw = store()?.getItem(key) ?? void 0;
201
+ const parsed = raw === void 0 ? [] : JSON.parse(raw);
202
+ return Array.isArray(parsed) ? parsed : [];
203
+ } catch {
204
+ return [];
205
+ }
206
+ };
207
+ const saveJson = (key, value) => {
208
+ const storage = store();
209
+ if (storage === void 0) {
210
+ return;
211
+ }
212
+ try {
213
+ storage.setItem(key, JSON.stringify(value));
214
+ } catch {
215
+ }
216
+ };
217
+ const usePersistedList = function(key) {
218
+ const $ = c(8);
219
+ let t0;
220
+ if ($[0] !== key) {
221
+ t0 = () => loadJsonArray(key);
222
+ $[0] = key;
223
+ $[1] = t0;
224
+ } else {
225
+ t0 = $[1];
226
+ }
227
+ const [value, setValue] = useState(t0);
228
+ let t1;
229
+ let t2;
230
+ if ($[2] !== key || $[3] !== value) {
231
+ t1 = () => {
232
+ saveJson(key, value);
233
+ };
234
+ t2 = [key, value];
235
+ $[2] = key;
236
+ $[3] = value;
237
+ $[4] = t1;
238
+ $[5] = t2;
239
+ } else {
240
+ t1 = $[4];
241
+ t2 = $[5];
242
+ }
243
+ useEffect(t1, t2);
244
+ let t3;
245
+ if ($[6] !== value) {
246
+ t3 = [value, setValue];
247
+ $[6] = value;
248
+ $[7] = t3;
249
+ } else {
250
+ t3 = $[7];
251
+ }
252
+ return t3;
253
+ };
254
+ const newId = (prefix) => globalThis.crypto?.randomUUID?.() ?? `${prefix}-${globalThis.performance.now().toString(36)}`;
255
+
256
+ const SQL_KEYWORDS = ["ORDER BY", "GROUP BY", "LEFT JOIN", "RIGHT JOIN", "INNER JOIN", "OUTER JOIN", "CROSS JOIN", "EXPLAIN QUERY PLAN", "SELECT", "DISTINCT", "FROM", "WHERE", "HAVING", "LIMIT", "OFFSET", "JOIN", "ON", "AND", "OR", "AS", "ASC", "DESC", "WITH", "UNION", "EXPLAIN"];
257
+ const SQL_NEWLINE_CLAUSES = ["FROM", "WHERE", "ORDER BY", "GROUP BY", "HAVING", "LIMIT", "OFFSET", "UNION", "JOIN", "LEFT JOIN", "RIGHT JOIN", "INNER JOIN", "OUTER JOIN", "CROSS JOIN"];
258
+ const SENTINEL = String.fromCodePoint(0);
259
+ const RESTORE_RE = new RegExp(`${SENTINEL}${String.raw`(\d+)`}${SENTINEL}`, "gu");
260
+ const KEYWORD_PATTERNS = SQL_KEYWORDS.map((keyword) => {
261
+ return {
262
+ pattern: new RegExp(String.raw`\b${keyword.replaceAll(" ", String.raw`\s+`)}\b`, "giu"),
263
+ replacement: keyword
264
+ };
265
+ });
266
+ const CLAUSE_PATTERNS = SQL_NEWLINE_CLAUSES.map((clause) => {
267
+ return {
268
+ pattern: new RegExp(String.raw`\s+${clause.replaceAll(" ", String.raw`\s+`)}\b`, "gu"),
269
+ replacement: `
270
+ ${clause}`
271
+ };
272
+ });
273
+ const formatSql = (sql) => {
274
+ const literals = [];
275
+ const withPlaceholders = sql.replaceAll(/'(?:[^']|'')*'/gu, (match) => {
276
+ literals.push(match);
277
+ return `${SENTINEL}${(literals.length - 1).toString()}${SENTINEL}`;
278
+ });
279
+ let out = withPlaceholders.replaceAll(/\s+/gu, " ").trim();
280
+ for (const {
281
+ pattern,
282
+ replacement
283
+ } of KEYWORD_PATTERNS) {
284
+ out = out.replaceAll(pattern, replacement);
285
+ }
286
+ for (const {
287
+ pattern,
288
+ replacement
289
+ } of CLAUSE_PATTERNS) {
290
+ out = out.replaceAll(pattern, replacement);
291
+ }
292
+ out = out.replaceAll(RESTORE_RE, (_match, index) => literals[Number(index)] ?? "");
293
+ return out;
294
+ };
295
+
296
+ const MAX_SUGGESTIONS = 8;
297
+ const KEYWORDS = ["AND", "ASC", "AS", "BY", "COUNT", "DESC", "DISTINCT", "EXPLAIN", "FROM", "GROUP", "HAVING", "INNER", "JOIN", "LEFT", "LIKE", "LIMIT", "NOT", "NULL", "OFFSET", "ON", "ORDER", "OR", "QUERY", "SELECT", "WHERE", "WITH"];
298
+ const COLUMN_CLAUSES = /* @__PURE__ */ new Set(["AND", "BY", "HAVING", "ON", "OR", "SELECT", "WHERE"]);
299
+ const WORD_CHAR = /[\w$]/;
300
+ const isWordChar = (char) => char !== void 0 && WORD_CHAR.test(char);
301
+ const trailingWord = (text) => {
302
+ let start = text.length;
303
+ while (start > 0 && isWordChar(text[start - 1])) {
304
+ start -= 1;
305
+ }
306
+ return text.slice(start);
307
+ };
308
+ const tokenAt = (value, caret) => {
309
+ let start = caret;
310
+ while (start > 0 && isWordChar(value[start - 1])) {
311
+ start -= 1;
312
+ }
313
+ return {
314
+ end: caret,
315
+ start,
316
+ text: value.slice(start, caret)
317
+ };
318
+ };
319
+ const prefersColumns = (value, start) => {
320
+ const before = value.slice(0, start).trimEnd();
321
+ return before.endsWith(".") || COLUMN_CLAUSES.has(trailingWord(before).toUpperCase());
322
+ };
323
+ const qualifierTable = (value, start) => {
324
+ const before = value.slice(0, start);
325
+ if (!before.endsWith(".")) {
326
+ return void 0;
327
+ }
328
+ const name = trailingWord(before.slice(0, -1));
329
+ return name === "" ? void 0 : name;
330
+ };
331
+ const matches = (candidate, needle) => candidate.toLowerCase().startsWith(needle.toLowerCase());
332
+ const takeMatches = (names, needle, max, make) => {
333
+ const out = [];
334
+ for (const name of names) {
335
+ if (matches(name, needle)) {
336
+ out.push(make(name));
337
+ if (out.length >= max) {
338
+ break;
339
+ }
340
+ }
341
+ }
342
+ return out;
343
+ };
344
+ const takeColumnMatches = (schema, needle, max) => {
345
+ const seen = /* @__PURE__ */ new Map();
346
+ for (const [table, columns] of Object.entries(schema.columns)) {
347
+ for (const column of columns) {
348
+ if (matches(column, needle) && !seen.has(column)) {
349
+ seen.set(column, table);
350
+ if (seen.size >= max) {
351
+ return [...seen].map(([column_, table_]) => {
352
+ return {
353
+ detail: table_,
354
+ kind: "column",
355
+ label: column_
356
+ };
357
+ });
358
+ }
359
+ }
360
+ }
361
+ }
362
+ return [...seen].map(([column, table]) => {
363
+ return {
364
+ detail: table,
365
+ kind: "column",
366
+ label: column
367
+ };
368
+ });
369
+ };
370
+ const suggestionsFor = (value, caret, schema) => {
371
+ const span = tokenAt(value, caret);
372
+ const qualifier = qualifierTable(value, span.start);
373
+ if (qualifier !== void 0) {
374
+ const owned = schema.columns[qualifier] ?? schema.columns[qualifier.toLowerCase()] ?? [];
375
+ return takeMatches(owned, span.text, MAX_SUGGESTIONS, (column) => {
376
+ return {
377
+ detail: qualifier,
378
+ kind: "column",
379
+ label: column
380
+ };
381
+ });
382
+ }
383
+ if (span.text === "" && !prefersColumns(value, span.start)) {
384
+ return [];
385
+ }
386
+ const tableHits = takeMatches(schema.tables, span.text, MAX_SUGGESTIONS, (table) => {
387
+ return {
388
+ kind: "table",
389
+ label: table
390
+ };
391
+ });
392
+ const columnHits = takeColumnMatches(schema, span.text, MAX_SUGGESTIONS);
393
+ const keywordHits = KEYWORDS.filter((keyword) => span.text !== "" && matches(keyword, span.text) && keyword.toLowerCase() !== span.text.toLowerCase()).map((keyword) => {
394
+ return {
395
+ kind: "keyword",
396
+ label: keyword
397
+ };
398
+ });
399
+ const ordered = prefersColumns(value, span.start) ? [...columnHits, ...tableHits, ...keywordHits] : [...tableHits, ...columnHits, ...keywordHits];
400
+ return ordered.slice(0, MAX_SUGGESTIONS);
401
+ };
402
+ const acceptSuggestion = (value, caret, suggestion) => {
403
+ const span = tokenAt(value, caret);
404
+ const next = value.slice(0, span.start) + suggestion.label + value.slice(span.end);
405
+ return {
406
+ caret: span.start + suggestion.label.length,
407
+ value: next
408
+ };
409
+ };
410
+
411
+ const MIRROR_PROPS = ["boxSizing", "borderBottomWidth", "borderLeftWidth", "borderRightWidth", "borderTopWidth", "fontFamily", "fontSize", "fontStyle", "fontWeight", "letterSpacing", "lineHeight", "paddingBottom", "paddingLeft", "paddingRight", "paddingTop", "tabSize", "textIndent", "textTransform", "width", "wordSpacing"];
412
+ const caretOffset = (textarea, caret) => {
413
+ const computed = globalThis.getComputedStyle(textarea);
414
+ const mirror = globalThis.document.createElement("div");
415
+ const {
416
+ style
417
+ } = mirror;
418
+ style.position = "absolute";
419
+ style.visibility = "hidden";
420
+ style.whiteSpace = "pre-wrap";
421
+ style.overflowWrap = "break-word";
422
+ for (const property of MIRROR_PROPS) {
423
+ style[property] = computed[property];
424
+ }
425
+ mirror.textContent = textarea.value.slice(0, Math.max(0, caret));
426
+ const marker = globalThis.document.createElement("span");
427
+ marker.textContent = textarea.value.slice(Math.max(0, caret)) || ".";
428
+ mirror.append(marker);
429
+ globalThis.document.body.append(mirror);
430
+ const left = marker.offsetLeft - textarea.scrollLeft;
431
+ const top = marker.offsetTop - textarea.scrollTop;
432
+ mirror.remove();
433
+ return {
434
+ left,
435
+ top
436
+ };
437
+ };
438
+ const useSqlAutocomplete = (schema, textareaRef, setValue) => {
439
+ const $ = c(13);
440
+ const [state, setState] = useState(null);
441
+ let t0;
442
+ if ($[0] !== schema || $[1] !== textareaRef) {
443
+ t0 = (value, caret) => {
444
+ const suggestions = suggestionsFor(value, caret, schema);
445
+ if (suggestions.length === 0) {
446
+ setState(null);
447
+ return;
448
+ }
449
+ const node = textareaRef.current;
450
+ const {
451
+ left,
452
+ top
453
+ } = node === null ? {
454
+ left: 0,
455
+ top: 0
456
+ } : caretOffset(node, caret);
457
+ setState({
458
+ active: 0,
459
+ left,
460
+ suggestions,
461
+ top
462
+ });
463
+ };
464
+ $[0] = schema;
465
+ $[1] = textareaRef;
466
+ $[2] = t0;
467
+ } else {
468
+ t0 = $[2];
469
+ }
470
+ const refresh = t0;
471
+ let t1;
472
+ if ($[3] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) {
473
+ t1 = () => {
474
+ setState(null);
475
+ };
476
+ $[3] = t1;
477
+ } else {
478
+ t1 = $[3];
479
+ }
480
+ const close = t1;
481
+ let t2;
482
+ if ($[4] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) {
483
+ t2 = (delta) => {
484
+ setState((current) => {
485
+ if (current === null) {
486
+ return current;
487
+ }
488
+ const count = current.suggestions.length;
489
+ const active = (current.active + delta + count) % count;
490
+ return {
491
+ ...current,
492
+ active
493
+ };
494
+ });
495
+ };
496
+ $[4] = t2;
497
+ } else {
498
+ t2 = $[4];
499
+ }
500
+ const move = t2;
501
+ let t3;
502
+ if ($[5] !== setValue || $[6] !== state || $[7] !== textareaRef) {
503
+ t3 = () => {
504
+ const textarea = textareaRef.current;
505
+ if (state === null || textarea === null) {
506
+ return false;
507
+ }
508
+ const chosen = state.suggestions[state.active];
509
+ if (chosen === void 0) {
510
+ return false;
511
+ }
512
+ const next = acceptSuggestion(textarea.value, textarea.selectionStart, chosen);
513
+ setValue(next.value);
514
+ setState(null);
515
+ requestAnimationFrame(() => {
516
+ const node_0 = textareaRef.current;
517
+ if (node_0 !== null) {
518
+ node_0.focus();
519
+ node_0.setSelectionRange(next.caret, next.caret);
520
+ }
521
+ });
522
+ return true;
523
+ };
524
+ $[5] = setValue;
525
+ $[6] = state;
526
+ $[7] = textareaRef;
527
+ $[8] = t3;
528
+ } else {
529
+ t3 = $[8];
530
+ }
531
+ const commit = t3;
532
+ let t4;
533
+ if ($[9] !== commit || $[10] !== refresh || $[11] !== state) {
534
+ t4 = {
535
+ close,
536
+ commit,
537
+ move,
538
+ refresh,
539
+ state
540
+ };
541
+ $[9] = commit;
542
+ $[10] = refresh;
543
+ $[11] = state;
544
+ $[12] = t4;
545
+ } else {
546
+ t4 = $[12];
547
+ }
548
+ return t4;
549
+ };
550
+ const KIND_LABEL = {
551
+ column: "column",
552
+ keyword: "keyword",
553
+ table: "table"
554
+ };
555
+ const CARET_LINE_OFFSET = 20;
556
+ const AutocompletePopover = (t0) => {
557
+ const $ = c(17);
558
+ const {
559
+ listboxId,
560
+ onPick,
561
+ state
562
+ } = t0;
563
+ const t = useT();
564
+ let t1;
565
+ if ($[0] !== state) {
566
+ t1 = state === null ? void 0 : {
567
+ left: state.left,
568
+ top: state.top + CARET_LINE_OFFSET
569
+ };
570
+ $[0] = state;
571
+ $[1] = t1;
572
+ } else {
573
+ t1 = $[1];
574
+ }
575
+ const style = t1;
576
+ if (state === null) {
577
+ return null;
578
+ }
579
+ let t2;
580
+ if ($[2] !== listboxId || $[3] !== onPick || $[4] !== state.active || $[5] !== state.suggestions || $[6] !== t) {
581
+ let t32;
582
+ if ($[8] !== listboxId || $[9] !== onPick || $[10] !== state.active || $[11] !== t) {
583
+ t32 = (suggestion, index) => /* @__PURE__ */ jsxDEV("li", {
584
+ "aria-selected": index === state.active,
585
+ className: cn("flex cursor-pointer items-center gap-2 px-2 py-1 text-[13px]", index === state.active ? "bg-accent text-accent-foreground" : "text-foreground"),
586
+ "data-testid": "sql-autocomplete-item",
587
+ id: `${listboxId}-opt-${index.toString()}`,
588
+ onMouseDown: (event) => {
589
+ event.preventDefault();
590
+ onPick(index);
591
+ },
592
+ role: "option",
593
+ children: [/* @__PURE__ */ jsxDEV("span", {
594
+ className: "truncate font-mono",
595
+ children: suggestion.label
596
+ }, void 0, false), suggestion.detail !== void 0 && /* @__PURE__ */ jsxDEV("span", {
597
+ className: "ms-auto truncate text-xs text-muted-foreground",
598
+ children: suggestion.detail
599
+ }, void 0, false), /* @__PURE__ */ jsxDEV("span", {
600
+ className: "shrink-0 font-mono text-[10px] tracking-wide text-muted-foreground/70 uppercase",
601
+ children: t(KIND_LABEL[suggestion.kind])
602
+ }, void 0, false)]
603
+ }, `${suggestion.kind}:${suggestion.label}`, true);
604
+ $[8] = listboxId;
605
+ $[9] = onPick;
606
+ $[10] = state.active;
607
+ $[11] = t;
608
+ $[12] = t32;
609
+ } else {
610
+ t32 = $[12];
611
+ }
612
+ t2 = state.suggestions.map(t32);
613
+ $[2] = listboxId;
614
+ $[3] = onPick;
615
+ $[4] = state.active;
616
+ $[5] = state.suggestions;
617
+ $[6] = t;
618
+ $[7] = t2;
619
+ } else {
620
+ t2 = $[7];
621
+ }
622
+ let t3;
623
+ if ($[13] !== listboxId || $[14] !== style || $[15] !== t2) {
624
+ t3 = /* @__PURE__ */ jsxDEV("ul", {
625
+ className: "absolute z-30 max-h-56 w-72 overflow-auto rounded-md border border-border bg-popover py-1 text-popover-foreground shadow-md",
626
+ "data-testid": "sql-autocomplete",
627
+ id: listboxId,
628
+ role: "listbox",
629
+ style,
630
+ children: t2
631
+ }, void 0, false);
632
+ $[13] = listboxId;
633
+ $[14] = style;
634
+ $[15] = t2;
635
+ $[16] = t3;
636
+ } else {
637
+ t3 = $[16];
638
+ }
639
+ return t3;
640
+ };
641
+
642
+ const TEMPLATES = [{
643
+ label: "List tables",
644
+ sql: "SELECT name FROM sqlite_master WHERE type = 'table' ORDER BY name;"
645
+ }, {
646
+ label: "Table row count",
647
+ sql: "SELECT COUNT(*) AS rows FROM messages;"
648
+ }, {
649
+ label: "Recent rows",
650
+ sql: "SELECT id, _creationTime, __doc__ FROM messages ORDER BY _creationTime DESC LIMIT 50;"
651
+ }, {
652
+ label: "Index list",
653
+ sql: "SELECT name, tbl_name FROM sqlite_master WHERE type = 'index' ORDER BY tbl_name;"
654
+ }];
655
+ const QueryRow = (t0) => {
656
+ const $ = c(29);
657
+ const {
658
+ active,
659
+ onDelete,
660
+ onSelect,
661
+ query
662
+ } = t0;
663
+ const t = useT();
664
+ let t1;
665
+ if ($[0] !== onSelect || $[1] !== query.id) {
666
+ t1 = () => {
667
+ onSelect(query.id);
668
+ };
669
+ $[0] = onSelect;
670
+ $[1] = query.id;
671
+ $[2] = t1;
672
+ } else {
673
+ t1 = $[2];
674
+ }
675
+ const onClick = t1;
676
+ let t2;
677
+ if ($[3] !== onDelete || $[4] !== query.id) {
678
+ t2 = (event) => {
679
+ event.stopPropagation();
680
+ onDelete(query.id);
681
+ };
682
+ $[3] = onDelete;
683
+ $[4] = query.id;
684
+ $[5] = t2;
685
+ } else {
686
+ t2 = $[5];
687
+ }
688
+ const onDeleteClick = t2;
689
+ const t3 = active ? "bg-sidebar-accent font-medium text-foreground" : "text-muted-foreground";
690
+ let t4;
691
+ if ($[6] !== t3) {
692
+ t4 = cn("flex min-w-0 flex-1 items-center gap-2 rounded-md px-2 py-1.5 text-start text-[13px] outline-none transition-colors hover:bg-sidebar-accent focus-visible:bg-sidebar-accent", t3);
693
+ $[6] = t3;
694
+ $[7] = t4;
695
+ } else {
696
+ t4 = $[7];
697
+ }
698
+ const t5 = `sql-query-${query.id}`;
699
+ let t6;
700
+ if ($[8] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) {
701
+ t6 = /* @__PURE__ */ jsxDEV("svg", {
702
+ "aria-hidden": "true",
703
+ className: "size-3.5 shrink-0 opacity-70",
704
+ fill: "none",
705
+ stroke: "currentColor",
706
+ strokeLinecap: "round",
707
+ strokeLinejoin: "round",
708
+ strokeWidth: 1.6,
709
+ viewBox: "0 0 24 24",
710
+ children: /* @__PURE__ */ jsxDEV("path", {
711
+ d: "M4 6h16M4 12h10M4 18h7"
712
+ }, void 0, false)
713
+ }, void 0, false);
714
+ $[8] = t6;
715
+ } else {
716
+ t6 = $[8];
717
+ }
718
+ let t7;
719
+ if ($[9] !== query.name) {
720
+ t7 = /* @__PURE__ */ jsxDEV("span", {
721
+ className: "truncate",
722
+ children: query.name
723
+ }, void 0, false);
724
+ $[9] = query.name;
725
+ $[10] = t7;
726
+ } else {
727
+ t7 = $[10];
728
+ }
729
+ let t8;
730
+ if ($[11] !== active || $[12] !== onClick || $[13] !== t4 || $[14] !== t5 || $[15] !== t7) {
731
+ t8 = /* @__PURE__ */ jsxDEV("button", {
732
+ "aria-pressed": active,
733
+ className: t4,
734
+ "data-testid": t5,
735
+ onClick,
736
+ type: "button",
737
+ children: [t6, t7]
738
+ }, void 0, true);
739
+ $[11] = active;
740
+ $[12] = onClick;
741
+ $[13] = t4;
742
+ $[14] = t5;
743
+ $[15] = t7;
744
+ $[16] = t8;
745
+ } else {
746
+ t8 = $[16];
747
+ }
748
+ let t9;
749
+ if ($[17] !== t) {
750
+ t9 = t("Delete query");
751
+ $[17] = t;
752
+ $[18] = t9;
753
+ } else {
754
+ t9 = $[18];
755
+ }
756
+ let t10;
757
+ if ($[19] !== t) {
758
+ t10 = t("Delete query");
759
+ $[19] = t;
760
+ $[20] = t10;
761
+ } else {
762
+ t10 = $[20];
763
+ }
764
+ let t11;
765
+ if ($[21] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) {
766
+ t11 = /* @__PURE__ */ jsxDEV("svg", {
767
+ "aria-hidden": "true",
768
+ className: "size-3.5",
769
+ fill: "none",
770
+ stroke: "currentColor",
771
+ strokeLinecap: "round",
772
+ strokeLinejoin: "round",
773
+ strokeWidth: 1.6,
774
+ viewBox: "0 0 24 24",
775
+ children: /* @__PURE__ */ jsxDEV("path", {
776
+ d: "M5 7h14M9 7V5h6v2m-1 0v12H10V7M7 7v13h10V7"
777
+ }, void 0, false)
778
+ }, void 0, false);
779
+ $[21] = t11;
780
+ } else {
781
+ t11 = $[21];
782
+ }
783
+ let t12;
784
+ if ($[22] !== onDeleteClick || $[23] !== t10 || $[24] !== t9) {
785
+ t12 = /* @__PURE__ */ jsxDEV("button", {
786
+ "aria-label": t9,
787
+ className: "me-1 hidden size-6 shrink-0 items-center justify-center rounded text-muted-foreground hover:bg-destructive/10 hover:text-destructive group-hover/q:flex",
788
+ onClick: onDeleteClick,
789
+ title: t10,
790
+ type: "button",
791
+ children: t11
792
+ }, void 0, false);
793
+ $[22] = onDeleteClick;
794
+ $[23] = t10;
795
+ $[24] = t9;
796
+ $[25] = t12;
797
+ } else {
798
+ t12 = $[25];
799
+ }
800
+ let t13;
801
+ if ($[26] !== t12 || $[27] !== t8) {
802
+ t13 = /* @__PURE__ */ jsxDEV("li", {
803
+ className: "group/q flex items-center",
804
+ children: [t8, t12]
805
+ }, void 0, true);
806
+ $[26] = t12;
807
+ $[27] = t8;
808
+ $[28] = t13;
809
+ } else {
810
+ t13 = $[28];
811
+ }
812
+ return t13;
813
+ };
814
+ const SqlQuerySidebar = (t0) => {
815
+ const $ = c(58);
816
+ const {
817
+ activeId,
818
+ history,
819
+ listRef,
820
+ onClearHistory,
821
+ onDelete,
822
+ onLoadHistory,
823
+ onLoadTemplate,
824
+ onNew,
825
+ onSearchChange,
826
+ onSelect,
827
+ queries,
828
+ search
829
+ } = t0;
830
+ const t = useT();
831
+ let t1;
832
+ if ($[0] !== queries || $[1] !== search) {
833
+ const needle = search.trim().toLowerCase();
834
+ t1 = needle === "" ? [...queries] : queries.filter((query) => query.name.toLowerCase().includes(needle));
835
+ $[0] = queries;
836
+ $[1] = search;
837
+ $[2] = t1;
838
+ } else {
839
+ t1 = $[2];
840
+ }
841
+ const filtered = t1;
842
+ let t2;
843
+ if ($[3] !== t) {
844
+ t2 = t("Search queries…");
845
+ $[3] = t;
846
+ $[4] = t2;
847
+ } else {
848
+ t2 = $[4];
849
+ }
850
+ let t3;
851
+ if ($[5] !== onSearchChange || $[6] !== search || $[7] !== t2) {
852
+ t3 = /* @__PURE__ */ jsxDEV("input", {
853
+ className: "h-8 min-w-0 flex-1 rounded-md border border-border bg-background px-2 text-xs outline-none focus-visible:border-ring",
854
+ "data-testid": "sql-search",
855
+ onChange: onSearchChange,
856
+ placeholder: t2,
857
+ type: "text",
858
+ value: search
859
+ }, void 0, false);
860
+ $[5] = onSearchChange;
861
+ $[6] = search;
862
+ $[7] = t2;
863
+ $[8] = t3;
864
+ } else {
865
+ t3 = $[8];
866
+ }
867
+ let t4;
868
+ if ($[9] !== t) {
869
+ t4 = t("New query");
870
+ $[9] = t;
871
+ $[10] = t4;
872
+ } else {
873
+ t4 = $[10];
874
+ }
875
+ let t5;
876
+ if ($[11] !== t) {
877
+ t5 = t("New query");
878
+ $[11] = t;
879
+ $[12] = t5;
880
+ } else {
881
+ t5 = $[12];
882
+ }
883
+ let t6;
884
+ if ($[13] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) {
885
+ t6 = /* @__PURE__ */ jsxDEV("svg", {
886
+ "aria-hidden": "true",
887
+ className: "size-4",
888
+ fill: "none",
889
+ stroke: "currentColor",
890
+ strokeLinecap: "round",
891
+ strokeLinejoin: "round",
892
+ strokeWidth: 1.7,
893
+ viewBox: "0 0 24 24",
894
+ children: /* @__PURE__ */ jsxDEV("path", {
895
+ d: "M12 5v14M5 12h14"
896
+ }, void 0, false)
897
+ }, void 0, false);
898
+ $[13] = t6;
899
+ } else {
900
+ t6 = $[13];
901
+ }
902
+ let t7;
903
+ if ($[14] !== onNew || $[15] !== t4 || $[16] !== t5) {
904
+ t7 = /* @__PURE__ */ jsxDEV("button", {
905
+ "aria-label": t4,
906
+ className: "flex size-8 shrink-0 items-center justify-center rounded-md border border-border text-foreground outline-none transition-colors hover:bg-accent focus-visible:bg-accent",
907
+ "data-testid": "sql-new",
908
+ onClick: onNew,
909
+ title: t5,
910
+ type: "button",
911
+ children: t6
912
+ }, void 0, false);
913
+ $[14] = onNew;
914
+ $[15] = t4;
915
+ $[16] = t5;
916
+ $[17] = t7;
917
+ } else {
918
+ t7 = $[17];
919
+ }
920
+ let t8;
921
+ if ($[18] !== t3 || $[19] !== t7) {
922
+ t8 = /* @__PURE__ */ jsxDEV("div", {
923
+ className: "flex shrink-0 items-center gap-2 border-b border-border p-2",
924
+ children: [t3, t7]
925
+ }, void 0, true);
926
+ $[18] = t3;
927
+ $[19] = t7;
928
+ $[20] = t8;
929
+ } else {
930
+ t8 = $[20];
931
+ }
932
+ let t9;
933
+ if ($[21] !== t) {
934
+ t9 = t("Private");
935
+ $[21] = t;
936
+ $[22] = t9;
937
+ } else {
938
+ t9 = $[22];
939
+ }
940
+ let t10;
941
+ if ($[23] !== t9) {
942
+ t10 = /* @__PURE__ */ jsxDEV("p", {
943
+ className: "px-1 pb-1 font-mono text-[11px] tracking-wide text-muted-foreground uppercase",
944
+ children: t9
945
+ }, void 0, false);
946
+ $[23] = t9;
947
+ $[24] = t10;
948
+ } else {
949
+ t10 = $[24];
950
+ }
951
+ let t11;
952
+ if ($[25] !== activeId || $[26] !== filtered || $[27] !== listRef || $[28] !== onDelete || $[29] !== onSelect || $[30] !== t) {
953
+ t11 = filtered.length === 0 ? /* @__PURE__ */ jsxDEV("p", {
954
+ className: "px-1 py-2 text-xs text-muted-foreground",
955
+ "data-testid": "sql-private-empty",
956
+ children: t("No saved queries yet — they save to this browser as you type.")
957
+ }, void 0, false) : /* @__PURE__ */ jsxDEV("ul", {
958
+ className: "flex flex-col gap-px",
959
+ "data-testid": "sql-private",
960
+ ref: listRef,
961
+ children: filtered.map((query_0) => /* @__PURE__ */ jsxDEV(QueryRow, {
962
+ active: activeId === query_0.id,
963
+ onDelete,
964
+ onSelect,
965
+ query: query_0
966
+ }, query_0.id, false))
967
+ }, void 0, false);
968
+ $[25] = activeId;
969
+ $[26] = filtered;
970
+ $[27] = listRef;
971
+ $[28] = onDelete;
972
+ $[29] = onSelect;
973
+ $[30] = t;
974
+ $[31] = t11;
975
+ } else {
976
+ t11 = $[31];
977
+ }
978
+ let t12;
979
+ if ($[32] !== t10 || $[33] !== t11) {
980
+ t12 = /* @__PURE__ */ jsxDEV("div", {
981
+ children: [t10, t11]
982
+ }, void 0, true);
983
+ $[32] = t10;
984
+ $[33] = t11;
985
+ $[34] = t12;
986
+ } else {
987
+ t12 = $[34];
988
+ }
989
+ let t13;
990
+ if ($[35] !== t) {
991
+ t13 = t("Reference");
992
+ $[35] = t;
993
+ $[36] = t13;
994
+ } else {
995
+ t13 = $[36];
996
+ }
997
+ let t14;
998
+ if ($[37] !== t13) {
999
+ t14 = /* @__PURE__ */ jsxDEV("p", {
1000
+ className: "px-1 pb-1 font-mono text-[11px] tracking-wide text-muted-foreground uppercase",
1001
+ children: t13
1002
+ }, void 0, false);
1003
+ $[37] = t13;
1004
+ $[38] = t14;
1005
+ } else {
1006
+ t14 = $[38];
1007
+ }
1008
+ let t15;
1009
+ if ($[39] !== onLoadTemplate) {
1010
+ t15 = TEMPLATES.map((template) => /* @__PURE__ */ jsxDEV("li", {
1011
+ children: /* @__PURE__ */ jsxDEV("button", {
1012
+ className: "flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-start text-[13px] text-muted-foreground outline-none transition-colors hover:bg-sidebar-accent hover:text-foreground focus-visible:bg-sidebar-accent",
1013
+ "data-sql": template.sql,
1014
+ onClick: onLoadTemplate,
1015
+ type: "button",
1016
+ children: [/* @__PURE__ */ jsxDEV("svg", {
1017
+ "aria-hidden": "true",
1018
+ className: "size-3.5 shrink-0 opacity-70",
1019
+ fill: "none",
1020
+ stroke: "currentColor",
1021
+ strokeLinecap: "round",
1022
+ strokeLinejoin: "round",
1023
+ strokeWidth: 1.6,
1024
+ viewBox: "0 0 24 24",
1025
+ children: /* @__PURE__ */ jsxDEV("path", {
1026
+ d: "M7 4h7l4 4v12H7zM14 4v4h4"
1027
+ }, void 0, false)
1028
+ }, void 0, false), template.label]
1029
+ }, void 0, true)
1030
+ }, template.label, false));
1031
+ $[39] = onLoadTemplate;
1032
+ $[40] = t15;
1033
+ } else {
1034
+ t15 = $[40];
1035
+ }
1036
+ let t16;
1037
+ if ($[41] !== t15) {
1038
+ t16 = /* @__PURE__ */ jsxDEV("ul", {
1039
+ className: "flex flex-col gap-px",
1040
+ children: t15
1041
+ }, void 0, false);
1042
+ $[41] = t15;
1043
+ $[42] = t16;
1044
+ } else {
1045
+ t16 = $[42];
1046
+ }
1047
+ let t17;
1048
+ if ($[43] !== t14 || $[44] !== t16) {
1049
+ t17 = /* @__PURE__ */ jsxDEV("div", {
1050
+ children: [t14, t16]
1051
+ }, void 0, true);
1052
+ $[43] = t14;
1053
+ $[44] = t16;
1054
+ $[45] = t17;
1055
+ } else {
1056
+ t17 = $[45];
1057
+ }
1058
+ let t18;
1059
+ if ($[46] !== history || $[47] !== onClearHistory || $[48] !== onLoadHistory || $[49] !== t) {
1060
+ t18 = history.length > 0 && /* @__PURE__ */ jsxDEV("div", {
1061
+ children: [/* @__PURE__ */ jsxDEV("div", {
1062
+ className: "flex items-center justify-between px-1 pb-1",
1063
+ children: [/* @__PURE__ */ jsxDEV("p", {
1064
+ className: "font-mono text-[11px] tracking-wide text-muted-foreground uppercase",
1065
+ children: t("History")
1066
+ }, void 0, false), /* @__PURE__ */ jsxDEV("button", {
1067
+ className: "rounded px-1 text-[11px] text-muted-foreground outline-none transition-colors hover:text-foreground focus-visible:text-foreground",
1068
+ "data-testid": "sql-history-clear",
1069
+ onClick: onClearHistory,
1070
+ type: "button",
1071
+ children: t("Clear history")
1072
+ }, void 0, false)]
1073
+ }, void 0, true), /* @__PURE__ */ jsxDEV("ul", {
1074
+ className: "flex flex-col gap-px",
1075
+ "data-testid": "sql-history",
1076
+ children: history.map((entry) => /* @__PURE__ */ jsxDEV("li", {
1077
+ children: /* @__PURE__ */ jsxDEV("button", {
1078
+ className: "flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-start text-[13px] text-muted-foreground outline-none transition-colors hover:bg-sidebar-accent hover:text-foreground focus-visible:bg-sidebar-accent",
1079
+ "data-sql": entry.sql,
1080
+ "data-testid": "sql-history-item",
1081
+ onClick: onLoadHistory,
1082
+ title: entry.sql,
1083
+ type: "button",
1084
+ children: [/* @__PURE__ */ jsxDEV("svg", {
1085
+ "aria-hidden": "true",
1086
+ className: "size-3.5 shrink-0 opacity-70",
1087
+ fill: "none",
1088
+ stroke: "currentColor",
1089
+ strokeLinecap: "round",
1090
+ strokeLinejoin: "round",
1091
+ strokeWidth: 1.6,
1092
+ viewBox: "0 0 24 24",
1093
+ children: /* @__PURE__ */ jsxDEV("path", {
1094
+ d: "M12 8v4l3 2M12 21a9 9 0 1 1 0-18 9 9 0 0 1 0 18Z"
1095
+ }, void 0, false)
1096
+ }, void 0, false), /* @__PURE__ */ jsxDEV("span", {
1097
+ className: "truncate font-mono",
1098
+ children: entry.sql
1099
+ }, void 0, false)]
1100
+ }, void 0, true)
1101
+ }, `${entry.at.toString()}:${entry.sql}`, false))
1102
+ }, void 0, false)]
1103
+ }, void 0, true);
1104
+ $[46] = history;
1105
+ $[47] = onClearHistory;
1106
+ $[48] = onLoadHistory;
1107
+ $[49] = t;
1108
+ $[50] = t18;
1109
+ } else {
1110
+ t18 = $[50];
1111
+ }
1112
+ let t19;
1113
+ if ($[51] !== t12 || $[52] !== t17 || $[53] !== t18) {
1114
+ t19 = /* @__PURE__ */ jsxDEV("div", {
1115
+ className: "flex flex-1 flex-col gap-4 overflow-y-auto p-2",
1116
+ children: [t12, t17, t18]
1117
+ }, void 0, true);
1118
+ $[51] = t12;
1119
+ $[52] = t17;
1120
+ $[53] = t18;
1121
+ $[54] = t19;
1122
+ } else {
1123
+ t19 = $[54];
1124
+ }
1125
+ let t20;
1126
+ if ($[55] !== t19 || $[56] !== t8) {
1127
+ t20 = /* @__PURE__ */ jsxDEV("aside", {
1128
+ className: "flex h-full w-64 shrink-0 flex-col border-e border-border bg-sidebar",
1129
+ children: [t8, t19]
1130
+ }, void 0, true);
1131
+ $[55] = t19;
1132
+ $[56] = t8;
1133
+ $[57] = t20;
1134
+ } else {
1135
+ t20 = $[57];
1136
+ }
1137
+ return t20;
1138
+ };
1139
+
1140
+ const LIST_TABLES = adminRef(ADMIN_FUNCTIONS.listTables);
1141
+ const READ_TABLE_PAGE = adminRef(ADMIN_FUNCTIONS.readTablePage);
1142
+ const useSqlSchema = (shardKey) => {
1143
+ const $ = c(16);
1144
+ const client = useLunora();
1145
+ let t0;
1146
+ if ($[0] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) {
1147
+ t0 = [];
1148
+ $[0] = t0;
1149
+ } else {
1150
+ t0 = $[0];
1151
+ }
1152
+ const [tables, setTables] = useState(t0);
1153
+ let t1;
1154
+ if ($[1] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) {
1155
+ t1 = {};
1156
+ $[1] = t1;
1157
+ } else {
1158
+ t1 = $[1];
1159
+ }
1160
+ const [columns, setColumns] = useState(t1);
1161
+ let t2;
1162
+ if ($[2] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) {
1163
+ t2 = /* @__PURE__ */ new Set();
1164
+ $[2] = t2;
1165
+ } else {
1166
+ t2 = $[2];
1167
+ }
1168
+ const probed = useRef(t2);
1169
+ let t3;
1170
+ let t4;
1171
+ if ($[3] !== client || $[4] !== shardKey) {
1172
+ t3 = () => {
1173
+ const token = {
1174
+ cancelled: false
1175
+ };
1176
+ const load = async () => {
1177
+ try {
1178
+ const result = await client.query(LIST_TABLES, {}, callOptions(shardKey));
1179
+ if (!token.cancelled) {
1180
+ setTables(result.map(_temp$2));
1181
+ setColumns({});
1182
+ probed.current = /* @__PURE__ */ new Set();
1183
+ }
1184
+ } catch {
1185
+ if (!token.cancelled) {
1186
+ setTables([]);
1187
+ setColumns({});
1188
+ probed.current = /* @__PURE__ */ new Set();
1189
+ }
1190
+ }
1191
+ };
1192
+ fireAndForget(load());
1193
+ return () => {
1194
+ token.cancelled = true;
1195
+ };
1196
+ };
1197
+ t4 = [client, shardKey];
1198
+ $[3] = client;
1199
+ $[4] = shardKey;
1200
+ $[5] = t3;
1201
+ $[6] = t4;
1202
+ } else {
1203
+ t3 = $[5];
1204
+ t4 = $[6];
1205
+ }
1206
+ useEffect(t3, t4);
1207
+ let t5;
1208
+ if ($[7] !== client || $[8] !== shardKey) {
1209
+ t5 = (table_0) => {
1210
+ if (probed.current.has(table_0)) {
1211
+ return;
1212
+ }
1213
+ probed.current.add(table_0);
1214
+ const fetchColumns = async () => {
1215
+ try {
1216
+ const page = await client.query(READ_TABLE_PAGE, {
1217
+ limit: 1,
1218
+ offset: 0,
1219
+ table: table_0
1220
+ }, callOptions(shardKey));
1221
+ setColumns((previous) => ({
1222
+ ...previous,
1223
+ [table_0]: page.columns
1224
+ }));
1225
+ } catch {
1226
+ probed.current.delete(table_0);
1227
+ }
1228
+ };
1229
+ fireAndForget(fetchColumns());
1230
+ };
1231
+ $[7] = client;
1232
+ $[8] = shardKey;
1233
+ $[9] = t5;
1234
+ } else {
1235
+ t5 = $[9];
1236
+ }
1237
+ const probe = t5;
1238
+ let t6;
1239
+ if ($[10] !== columns || $[11] !== tables) {
1240
+ t6 = {
1241
+ columns,
1242
+ tables
1243
+ };
1244
+ $[10] = columns;
1245
+ $[11] = tables;
1246
+ $[12] = t6;
1247
+ } else {
1248
+ t6 = $[12];
1249
+ }
1250
+ const schema = t6;
1251
+ let t7;
1252
+ if ($[13] !== probe || $[14] !== schema) {
1253
+ t7 = {
1254
+ probe,
1255
+ schema
1256
+ };
1257
+ $[13] = probe;
1258
+ $[14] = schema;
1259
+ $[15] = t7;
1260
+ } else {
1261
+ t7 = $[15];
1262
+ }
1263
+ return t7;
1264
+ };
1265
+ const TABLE_REF = /\b(?:from|join|update|into)\s+([a-z_][\w$]*)|\b([a-z_][\w$]*)\s*\./gi;
1266
+ const referencedTables = (sql) => {
1267
+ const names = /* @__PURE__ */ new Set();
1268
+ TABLE_REF.lastIndex = 0;
1269
+ let match = TABLE_REF.exec(sql);
1270
+ while (match !== null) {
1271
+ names.add(match[1] ?? match[2]);
1272
+ match = TABLE_REF.exec(sql);
1273
+ }
1274
+ return [...names];
1275
+ };
1276
+ function _temp$2(table) {
1277
+ return table.name;
1278
+ }
1279
+
1280
+ const TABS_KEY = "lunora-studio-sql-tabs";
1281
+ const ACTIVE_KEY = "lunora-studio-sql-active-tab";
1282
+ const MAX_TABS = 12;
1283
+ const makeTab = (sql = "", name = "") => {
1284
+ return {
1285
+ activeId: null,
1286
+ id: newId("tab"),
1287
+ name,
1288
+ sql
1289
+ };
1290
+ };
1291
+ const usePersistedActive = () => {
1292
+ const $ = c(5);
1293
+ const [list, setList] = usePersistedList(ACTIVE_KEY);
1294
+ let t0;
1295
+ if ($[0] !== setList) {
1296
+ t0 = (action) => {
1297
+ setList((current) => {
1298
+ const previous = current[0] ?? "";
1299
+ const next = typeof action === "function" ? action(previous) : action;
1300
+ return [next];
1301
+ });
1302
+ };
1303
+ $[0] = setList;
1304
+ $[1] = t0;
1305
+ } else {
1306
+ t0 = $[1];
1307
+ }
1308
+ const setActive = t0;
1309
+ const t1 = list[0] ?? "";
1310
+ let t2;
1311
+ if ($[2] !== setActive || $[3] !== t1) {
1312
+ t2 = [t1, setActive];
1313
+ $[2] = setActive;
1314
+ $[3] = t1;
1315
+ $[4] = t2;
1316
+ } else {
1317
+ t2 = $[4];
1318
+ }
1319
+ return t2;
1320
+ };
1321
+ const usePersistedTabs = (seed) => {
1322
+ const [stored, setTabs] = usePersistedList(TABS_KEY);
1323
+ const [activeRaw, setActiveId] = usePersistedActive();
1324
+ const seedTab = useRef(null);
1325
+ if (stored.length === 0 && seedTab.current === null) {
1326
+ seedTab.current = seed();
1327
+ }
1328
+ useEffect(() => {
1329
+ if (stored.length === 0 && seedTab.current !== null) {
1330
+ const initial = seedTab.current;
1331
+ setTabs([initial]);
1332
+ }
1333
+ }, [setTabs, stored.length]);
1334
+ const tabs = stored.length === 0 && seedTab.current !== null ? [seedTab.current] : stored;
1335
+ const activeId = tabs.some((tab) => tab.id === activeRaw) ? activeRaw : tabs[0]?.id ?? "";
1336
+ return {
1337
+ activeId,
1338
+ setActiveId,
1339
+ setTabs,
1340
+ tabs
1341
+ };
1342
+ };
1343
+ const isDirty = (tab) => tab.activeId === null && tab.sql.trim() !== "";
1344
+ const addTab = (tabs, tab) => [...tabs, tab].slice(-MAX_TABS);
1345
+ const closeTab = (tabs, id, makeEmpty) => {
1346
+ const index = tabs.findIndex((tab) => tab.id === id);
1347
+ if (index === -1) {
1348
+ return {
1349
+ activeId: tabs[0]?.id ?? "",
1350
+ tabs: [...tabs]
1351
+ };
1352
+ }
1353
+ const remaining = tabs.filter((tab) => tab.id !== id);
1354
+ if (remaining.length === 0) {
1355
+ const fresh = makeEmpty();
1356
+ return {
1357
+ activeId: fresh.id,
1358
+ tabs: [fresh]
1359
+ };
1360
+ }
1361
+ const neighbour = remaining[Math.min(index, remaining.length - 1)];
1362
+ return {
1363
+ activeId: neighbour.id,
1364
+ tabs: remaining
1365
+ };
1366
+ };
1367
+ const closeOtherTabs = (tabs, id) => {
1368
+ const kept = tabs.filter((tab) => tab.id === id);
1369
+ return kept.length === 0 ? {
1370
+ activeId: tabs[0]?.id ?? "",
1371
+ tabs: [...tabs]
1372
+ } : {
1373
+ activeId: id,
1374
+ tabs: kept
1375
+ };
1376
+ };
1377
+ const closeTabsToRight = (tabs, id, activeId) => {
1378
+ const index = tabs.findIndex((tab) => tab.id === id);
1379
+ if (index === -1) {
1380
+ return {
1381
+ activeId,
1382
+ tabs: [...tabs]
1383
+ };
1384
+ }
1385
+ const kept = tabs.slice(0, index + 1);
1386
+ return {
1387
+ activeId: kept.some((tab) => tab.id === activeId) ? activeId : id,
1388
+ tabs: kept
1389
+ };
1390
+ };
1391
+ const closeAllTabs = (makeEmpty) => {
1392
+ const fresh = makeEmpty();
1393
+ return {
1394
+ activeId: fresh.id,
1395
+ tabs: [fresh]
1396
+ };
1397
+ };
1398
+ const tabsClosedBy = (op, tabs, id) => {
1399
+ if (op === "others") {
1400
+ return tabs.filter((tab) => tab.id !== id);
1401
+ }
1402
+ if (op === "right") {
1403
+ const index = tabs.findIndex((tab) => tab.id === id);
1404
+ return index === -1 ? [] : tabs.slice(index + 1);
1405
+ }
1406
+ return [...tabs];
1407
+ };
1408
+
1409
+ const derivedTabLabel = (sql, untitled) => sql.trim() === "" ? untitled : (sql.split("\n")[0] ?? sql).slice(0, 24);
1410
+ const TabRenameInput = (t0) => {
1411
+ const $ = c(14);
1412
+ const {
1413
+ initial,
1414
+ onCancel,
1415
+ onCommit,
1416
+ placeholder,
1417
+ testId
1418
+ } = t0;
1419
+ const t = useT();
1420
+ const focusOnMount = _temp$1;
1421
+ let t1;
1422
+ if ($[0] !== onCommit) {
1423
+ t1 = (event) => {
1424
+ onCommit(event.currentTarget.value.trim());
1425
+ };
1426
+ $[0] = onCommit;
1427
+ $[1] = t1;
1428
+ } else {
1429
+ t1 = $[1];
1430
+ }
1431
+ const onBlur = t1;
1432
+ let t2;
1433
+ if ($[2] !== onCancel || $[3] !== onCommit) {
1434
+ t2 = (event_0) => {
1435
+ if (event_0.key === "Enter") {
1436
+ onCommit(event_0.currentTarget.value.trim());
1437
+ } else {
1438
+ if (event_0.key === "Escape") {
1439
+ onCancel();
1440
+ }
1441
+ }
1442
+ };
1443
+ $[2] = onCancel;
1444
+ $[3] = onCommit;
1445
+ $[4] = t2;
1446
+ } else {
1447
+ t2 = $[4];
1448
+ }
1449
+ const onKeyDown = t2;
1450
+ let t3;
1451
+ if ($[5] !== t) {
1452
+ t3 = t("Tab title");
1453
+ $[5] = t;
1454
+ $[6] = t3;
1455
+ } else {
1456
+ t3 = $[6];
1457
+ }
1458
+ let t4;
1459
+ if ($[7] !== initial || $[8] !== onBlur || $[9] !== onKeyDown || $[10] !== placeholder || $[11] !== t3 || $[12] !== testId) {
1460
+ t4 = /* @__PURE__ */ jsxDEV("input", {
1461
+ "aria-label": t3,
1462
+ className: "w-40 rounded border border-ring bg-background px-1 py-0.5 text-xs outline-none",
1463
+ "data-testid": testId,
1464
+ defaultValue: initial,
1465
+ onBlur,
1466
+ onKeyDown,
1467
+ placeholder,
1468
+ ref: focusOnMount,
1469
+ type: "text"
1470
+ }, void 0, false);
1471
+ $[7] = initial;
1472
+ $[8] = onBlur;
1473
+ $[9] = onKeyDown;
1474
+ $[10] = placeholder;
1475
+ $[11] = t3;
1476
+ $[12] = testId;
1477
+ $[13] = t4;
1478
+ } else {
1479
+ t4 = $[13];
1480
+ }
1481
+ return t4;
1482
+ };
1483
+ const TabCloseConfirm = (t0) => {
1484
+ const $ = c(29);
1485
+ const {
1486
+ idBase,
1487
+ onDiscard,
1488
+ onKeep
1489
+ } = t0;
1490
+ const t = useT();
1491
+ const t1 = `sql-tab-close-prompt-${idBase}`;
1492
+ let t2;
1493
+ if ($[0] !== t) {
1494
+ t2 = t("Discard?");
1495
+ $[0] = t;
1496
+ $[1] = t2;
1497
+ } else {
1498
+ t2 = $[1];
1499
+ }
1500
+ let t3;
1501
+ if ($[2] !== t2) {
1502
+ t3 = /* @__PURE__ */ jsxDEV("span", {
1503
+ className: "text-[11px] text-muted-foreground",
1504
+ children: t2
1505
+ }, void 0, false);
1506
+ $[2] = t2;
1507
+ $[3] = t3;
1508
+ } else {
1509
+ t3 = $[3];
1510
+ }
1511
+ let t4;
1512
+ if ($[4] !== t) {
1513
+ t4 = t("Discard changes");
1514
+ $[4] = t;
1515
+ $[5] = t4;
1516
+ } else {
1517
+ t4 = $[5];
1518
+ }
1519
+ const t5 = `sql-tab-close-confirm-${idBase}`;
1520
+ let t6;
1521
+ if ($[6] !== t) {
1522
+ t6 = t("Discard changes");
1523
+ $[6] = t;
1524
+ $[7] = t6;
1525
+ } else {
1526
+ t6 = $[7];
1527
+ }
1528
+ let t7;
1529
+ if ($[8] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) {
1530
+ t7 = /* @__PURE__ */ jsxDEV("svg", {
1531
+ "aria-hidden": "true",
1532
+ className: "size-3",
1533
+ fill: "none",
1534
+ stroke: "currentColor",
1535
+ strokeLinecap: "round",
1536
+ strokeLinejoin: "round",
1537
+ strokeWidth: 2.4,
1538
+ viewBox: "0 0 24 24",
1539
+ children: /* @__PURE__ */ jsxDEV("path", {
1540
+ d: "M5 13l4 4L19 7"
1541
+ }, void 0, false)
1542
+ }, void 0, false);
1543
+ $[8] = t7;
1544
+ } else {
1545
+ t7 = $[8];
1546
+ }
1547
+ let t8;
1548
+ if ($[9] !== onDiscard || $[10] !== t4 || $[11] !== t5 || $[12] !== t6) {
1549
+ t8 = /* @__PURE__ */ jsxDEV("button", {
1550
+ "aria-label": t4,
1551
+ className: "flex size-5 items-center justify-center rounded text-destructive hover:bg-destructive/10",
1552
+ "data-testid": t5,
1553
+ onClick: onDiscard,
1554
+ title: t6,
1555
+ type: "button",
1556
+ children: t7
1557
+ }, void 0, false);
1558
+ $[9] = onDiscard;
1559
+ $[10] = t4;
1560
+ $[11] = t5;
1561
+ $[12] = t6;
1562
+ $[13] = t8;
1563
+ } else {
1564
+ t8 = $[13];
1565
+ }
1566
+ let t9;
1567
+ if ($[14] !== t) {
1568
+ t9 = t("Keep editing");
1569
+ $[14] = t;
1570
+ $[15] = t9;
1571
+ } else {
1572
+ t9 = $[15];
1573
+ }
1574
+ const t10 = `sql-tab-close-cancel-${idBase}`;
1575
+ let t11;
1576
+ if ($[16] !== t) {
1577
+ t11 = t("Keep editing");
1578
+ $[16] = t;
1579
+ $[17] = t11;
1580
+ } else {
1581
+ t11 = $[17];
1582
+ }
1583
+ let t12;
1584
+ if ($[18] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) {
1585
+ t12 = /* @__PURE__ */ jsxDEV("svg", {
1586
+ "aria-hidden": "true",
1587
+ className: "size-3",
1588
+ fill: "none",
1589
+ stroke: "currentColor",
1590
+ strokeLinecap: "round",
1591
+ strokeLinejoin: "round",
1592
+ strokeWidth: 2.4,
1593
+ viewBox: "0 0 24 24",
1594
+ children: /* @__PURE__ */ jsxDEV("path", {
1595
+ d: "M6 6l12 12M18 6 6 18"
1596
+ }, void 0, false)
1597
+ }, void 0, false);
1598
+ $[18] = t12;
1599
+ } else {
1600
+ t12 = $[18];
1601
+ }
1602
+ let t13;
1603
+ if ($[19] !== onKeep || $[20] !== t10 || $[21] !== t11 || $[22] !== t9) {
1604
+ t13 = /* @__PURE__ */ jsxDEV("button", {
1605
+ "aria-label": t9,
1606
+ className: "flex size-5 items-center justify-center rounded text-muted-foreground hover:bg-accent",
1607
+ "data-testid": t10,
1608
+ onClick: onKeep,
1609
+ title: t11,
1610
+ type: "button",
1611
+ children: t12
1612
+ }, void 0, false);
1613
+ $[19] = onKeep;
1614
+ $[20] = t10;
1615
+ $[21] = t11;
1616
+ $[22] = t9;
1617
+ $[23] = t13;
1618
+ } else {
1619
+ t13 = $[23];
1620
+ }
1621
+ let t14;
1622
+ if ($[24] !== t1 || $[25] !== t13 || $[26] !== t3 || $[27] !== t8) {
1623
+ t14 = /* @__PURE__ */ jsxDEV("span", {
1624
+ className: "flex shrink-0 items-center gap-1",
1625
+ "data-testid": t1,
1626
+ role: "group",
1627
+ children: [t3, t8, t13]
1628
+ }, void 0, true);
1629
+ $[24] = t1;
1630
+ $[25] = t13;
1631
+ $[26] = t3;
1632
+ $[27] = t8;
1633
+ $[28] = t14;
1634
+ } else {
1635
+ t14 = $[28];
1636
+ }
1637
+ return t14;
1638
+ };
1639
+ const TabButton = (t0) => {
1640
+ const $ = c(47);
1641
+ const {
1642
+ active,
1643
+ canClose,
1644
+ onClose,
1645
+ onMenu,
1646
+ onRename,
1647
+ onSelect,
1648
+ tab
1649
+ } = t0;
1650
+ const t = useT();
1651
+ const [editing, setEditing] = useState(false);
1652
+ const [confirmingClose, setConfirmingClose] = useState(false);
1653
+ let t1;
1654
+ if ($[0] !== onMenu || $[1] !== tab.id) {
1655
+ t1 = (event) => {
1656
+ onMenu(tab.id, event);
1657
+ };
1658
+ $[0] = onMenu;
1659
+ $[1] = tab.id;
1660
+ $[2] = t1;
1661
+ } else {
1662
+ t1 = $[2];
1663
+ }
1664
+ const onContextMenu = t1;
1665
+ let t2;
1666
+ if ($[3] !== onSelect || $[4] !== tab.id) {
1667
+ t2 = () => {
1668
+ onSelect(tab.id);
1669
+ };
1670
+ $[3] = onSelect;
1671
+ $[4] = tab.id;
1672
+ $[5] = t2;
1673
+ } else {
1674
+ t2 = $[5];
1675
+ }
1676
+ const onClick = t2;
1677
+ let t3;
1678
+ if ($[6] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) {
1679
+ t3 = () => {
1680
+ setEditing(true);
1681
+ };
1682
+ $[6] = t3;
1683
+ } else {
1684
+ t3 = $[6];
1685
+ }
1686
+ const startEditing = t3;
1687
+ let t4;
1688
+ if ($[7] !== onClose || $[8] !== tab) {
1689
+ t4 = (event_0) => {
1690
+ event_0.stopPropagation();
1691
+ if (isDirty(tab)) {
1692
+ setConfirmingClose(true);
1693
+ } else {
1694
+ onClose(tab.id);
1695
+ }
1696
+ };
1697
+ $[7] = onClose;
1698
+ $[8] = tab;
1699
+ $[9] = t4;
1700
+ } else {
1701
+ t4 = $[9];
1702
+ }
1703
+ const onCloseClick = t4;
1704
+ let t5;
1705
+ if ($[10] !== onClose || $[11] !== tab.id) {
1706
+ t5 = (event_1) => {
1707
+ event_1.stopPropagation();
1708
+ setConfirmingClose(false);
1709
+ onClose(tab.id);
1710
+ };
1711
+ $[10] = onClose;
1712
+ $[11] = tab.id;
1713
+ $[12] = t5;
1714
+ } else {
1715
+ t5 = $[12];
1716
+ }
1717
+ const confirmClose = t5;
1718
+ let t6;
1719
+ if ($[13] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) {
1720
+ t6 = (event_2) => {
1721
+ event_2.stopPropagation();
1722
+ setConfirmingClose(false);
1723
+ };
1724
+ $[13] = t6;
1725
+ } else {
1726
+ t6 = $[13];
1727
+ }
1728
+ const cancelClose = t6;
1729
+ let t7;
1730
+ if ($[14] !== onRename || $[15] !== tab.id) {
1731
+ t7 = (name) => {
1732
+ onRename(tab.id, name);
1733
+ setEditing(false);
1734
+ };
1735
+ $[14] = onRename;
1736
+ $[15] = tab.id;
1737
+ $[16] = t7;
1738
+ } else {
1739
+ t7 = $[16];
1740
+ }
1741
+ const commitRename = t7;
1742
+ let t8;
1743
+ if ($[17] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) {
1744
+ t8 = () => {
1745
+ setEditing(false);
1746
+ };
1747
+ $[17] = t8;
1748
+ } else {
1749
+ t8 = $[17];
1750
+ }
1751
+ const cancelRename = t8;
1752
+ let t9;
1753
+ if ($[18] !== tab.name) {
1754
+ t9 = tab.name.trim();
1755
+ $[18] = tab.name;
1756
+ $[19] = t9;
1757
+ } else {
1758
+ t9 = $[19];
1759
+ }
1760
+ const custom = t9;
1761
+ let t10;
1762
+ if ($[20] !== t || $[21] !== tab.sql) {
1763
+ t10 = derivedTabLabel(tab.sql, t("Untitled"));
1764
+ $[20] = t;
1765
+ $[21] = tab.sql;
1766
+ $[22] = t10;
1767
+ } else {
1768
+ t10 = $[22];
1769
+ }
1770
+ const derived = t10;
1771
+ const t11 = active ? "bg-background text-foreground" : "bg-muted/40 text-muted-foreground hover:text-foreground";
1772
+ let t12;
1773
+ if ($[23] !== t11) {
1774
+ t12 = cn("group/tab flex shrink-0 items-center gap-1 border-e border-border ps-3 pe-1.5 text-xs", t11);
1775
+ $[23] = t11;
1776
+ $[24] = t12;
1777
+ } else {
1778
+ t12 = $[24];
1779
+ }
1780
+ const t13 = `sql-tab-${tab.id}`;
1781
+ let t14;
1782
+ if ($[25] !== active || $[26] !== commitRename || $[27] !== custom || $[28] !== derived || $[29] !== editing || $[30] !== onClick || $[31] !== t || $[32] !== tab.id) {
1783
+ t14 = editing ? /* @__PURE__ */ jsxDEV(TabRenameInput, {
1784
+ initial: custom,
1785
+ onCancel: cancelRename,
1786
+ onCommit: commitRename,
1787
+ placeholder: derived,
1788
+ testId: `sql-tab-rename-${tab.id}`
1789
+ }, void 0, false) : /* @__PURE__ */ jsxDEV("button", {
1790
+ "aria-pressed": active,
1791
+ className: "max-w-40 truncate py-1.5 outline-none",
1792
+ "data-testid": `sql-tab-select-${tab.id}`,
1793
+ onClick,
1794
+ onDoubleClick: startEditing,
1795
+ title: t("Double-click to rename"),
1796
+ type: "button",
1797
+ children: custom === "" ? derived : custom
1798
+ }, void 0, false);
1799
+ $[25] = active;
1800
+ $[26] = commitRename;
1801
+ $[27] = custom;
1802
+ $[28] = derived;
1803
+ $[29] = editing;
1804
+ $[30] = onClick;
1805
+ $[31] = t;
1806
+ $[32] = tab.id;
1807
+ $[33] = t14;
1808
+ } else {
1809
+ t14 = $[33];
1810
+ }
1811
+ let t15;
1812
+ if ($[34] !== canClose || $[35] !== confirmClose || $[36] !== confirmingClose || $[37] !== onCloseClick || $[38] !== t || $[39] !== tab.id) {
1813
+ t15 = canClose && (confirmingClose ? /* @__PURE__ */ jsxDEV(TabCloseConfirm, {
1814
+ idBase: tab.id,
1815
+ onDiscard: confirmClose,
1816
+ onKeep: cancelClose
1817
+ }, void 0, false) : /* @__PURE__ */ jsxDEV("button", {
1818
+ "aria-label": t("Close tab"),
1819
+ className: "flex size-5 shrink-0 items-center justify-center rounded text-muted-foreground hover:bg-destructive/10 hover:text-destructive",
1820
+ "data-testid": `sql-tab-close-${tab.id}`,
1821
+ onClick: onCloseClick,
1822
+ title: t("Close tab"),
1823
+ type: "button",
1824
+ children: /* @__PURE__ */ jsxDEV("svg", {
1825
+ "aria-hidden": "true",
1826
+ className: "size-3",
1827
+ fill: "none",
1828
+ stroke: "currentColor",
1829
+ strokeLinecap: "round",
1830
+ strokeLinejoin: "round",
1831
+ strokeWidth: 2,
1832
+ viewBox: "0 0 24 24",
1833
+ children: /* @__PURE__ */ jsxDEV("path", {
1834
+ d: "M6 6l12 12M18 6 6 18"
1835
+ }, void 0, false)
1836
+ }, void 0, false)
1837
+ }, void 0, false));
1838
+ $[34] = canClose;
1839
+ $[35] = confirmClose;
1840
+ $[36] = confirmingClose;
1841
+ $[37] = onCloseClick;
1842
+ $[38] = t;
1843
+ $[39] = tab.id;
1844
+ $[40] = t15;
1845
+ } else {
1846
+ t15 = $[40];
1847
+ }
1848
+ let t16;
1849
+ if ($[41] !== onContextMenu || $[42] !== t12 || $[43] !== t13 || $[44] !== t14 || $[45] !== t15) {
1850
+ t16 = /* @__PURE__ */ jsxDEV("div", {
1851
+ className: t12,
1852
+ "data-testid": t13,
1853
+ onContextMenu,
1854
+ children: [t14, t15]
1855
+ }, void 0, true);
1856
+ $[41] = onContextMenu;
1857
+ $[42] = t12;
1858
+ $[43] = t13;
1859
+ $[44] = t14;
1860
+ $[45] = t15;
1861
+ $[46] = t16;
1862
+ } else {
1863
+ t16 = $[46];
1864
+ }
1865
+ return t16;
1866
+ };
1867
+ function _temp$1(node) {
1868
+ node?.focus();
1869
+ node?.select();
1870
+ }
1871
+
1872
+ const RUN_SQL = adminRef(ADMIN_FUNCTIONS.runSql);
1873
+ const STORAGE_KEY = "lunora-studio-sql-queries";
1874
+ const HISTORY_KEY = "lunora-studio-sql-history";
1875
+ const HISTORY_LIMIT = 25;
1876
+ const GUTTER_STYLE = {
1877
+ minWidth: "2.75rem",
1878
+ paddingInline: "0.5rem"
1879
+ };
1880
+ const DEFAULT_TAB_OUTPUT = {
1881
+ error: null,
1882
+ pane: "results",
1883
+ result: null
1884
+ };
1885
+ const SqlResultTable = (t0) => {
1886
+ const $ = c(16);
1887
+ const {
1888
+ result
1889
+ } = t0;
1890
+ if (result.columns.length === 0) {
1891
+ const t12 = result.rowCount === 0 ? "0 rows" : "";
1892
+ let t22;
1893
+ if ($[0] !== t12) {
1894
+ t22 = /* @__PURE__ */ jsxDEV("p", {
1895
+ className: "p-4 text-sm text-muted-foreground",
1896
+ children: t12
1897
+ }, void 0, false);
1898
+ $[0] = t12;
1899
+ $[1] = t22;
1900
+ } else {
1901
+ t22 = $[1];
1902
+ }
1903
+ return t22;
1904
+ }
1905
+ let t1;
1906
+ if ($[2] !== result.columns) {
1907
+ t1 = result.columns.map(_temp);
1908
+ $[2] = result.columns;
1909
+ $[3] = t1;
1910
+ } else {
1911
+ t1 = $[3];
1912
+ }
1913
+ let t2;
1914
+ if ($[4] !== t1) {
1915
+ t2 = /* @__PURE__ */ jsxDEV(TableHeader, {
1916
+ className: "sticky top-0 z-10 bg-muted",
1917
+ children: /* @__PURE__ */ jsxDEV(TableRow, {
1918
+ children: t1
1919
+ }, void 0, false)
1920
+ }, void 0, false);
1921
+ $[4] = t1;
1922
+ $[5] = t2;
1923
+ } else {
1924
+ t2 = $[5];
1925
+ }
1926
+ let t3;
1927
+ if ($[6] !== result.columns || $[7] !== result.rows) {
1928
+ let t42;
1929
+ if ($[9] !== result.columns) {
1930
+ t42 = (row, rowIndex) => /* @__PURE__ */ jsxDEV(TableRow, {
1931
+ "data-testid": "sql-row",
1932
+ children: result.columns.map((column_0) => /* @__PURE__ */ jsxDEV(TableCell, {
1933
+ className: "max-w-md truncate font-mono text-xs",
1934
+ children: /* @__PURE__ */ jsxDEV(CellValue, {
1935
+ value: row[column_0]
1936
+ }, void 0, false)
1937
+ }, column_0, false))
1938
+ }, rowIndex, false);
1939
+ $[9] = result.columns;
1940
+ $[10] = t42;
1941
+ } else {
1942
+ t42 = $[10];
1943
+ }
1944
+ t3 = result.rows.map(t42);
1945
+ $[6] = result.columns;
1946
+ $[7] = result.rows;
1947
+ $[8] = t3;
1948
+ } else {
1949
+ t3 = $[8];
1950
+ }
1951
+ let t4;
1952
+ if ($[11] !== t3) {
1953
+ t4 = /* @__PURE__ */ jsxDEV(TableBody, {
1954
+ children: t3
1955
+ }, void 0, false);
1956
+ $[11] = t3;
1957
+ $[12] = t4;
1958
+ } else {
1959
+ t4 = $[12];
1960
+ }
1961
+ let t5;
1962
+ if ($[13] !== t2 || $[14] !== t4) {
1963
+ t5 = /* @__PURE__ */ jsxDEV(Table, {
1964
+ "data-testid": "sql-rows",
1965
+ children: [t2, t4]
1966
+ }, void 0, true);
1967
+ $[13] = t2;
1968
+ $[14] = t4;
1969
+ $[15] = t5;
1970
+ } else {
1971
+ t5 = $[15];
1972
+ }
1973
+ return t5;
1974
+ };
1975
+ const SqlEditorPanel = ({
1976
+ initialShardKey
1977
+ }) => {
1978
+ const client = useLunora();
1979
+ const t = useT();
1980
+ const [queries, setQueries] = usePersistedList(STORAGE_KEY);
1981
+ const [history, setHistory] = usePersistedList(HISTORY_KEY);
1982
+ const [search, setSearch] = useState("");
1983
+ const seedTab = () => makeTab(TEMPLATES[0]?.sql ?? "");
1984
+ const {
1985
+ activeId: activeTabId,
1986
+ setActiveId: setActiveTabId,
1987
+ setTabs,
1988
+ tabs
1989
+ } = usePersistedTabs(seedTab);
1990
+ const [outputs, setOutputs] = useState({});
1991
+ const [shardKey, setShardKey] = useState(initialShardKey ?? "");
1992
+ const [running, setRunning] = useState(false);
1993
+ const [tabMenu, setTabMenu] = useState(null);
1994
+ const [pendingBulk, setPendingBulk] = useState(null);
1995
+ const gutterRef = useRef(null);
1996
+ const editorRef = useRef(null);
1997
+ const privateListRef = useRef(null);
1998
+ const listboxId = useId();
1999
+ const {
2000
+ probe,
2001
+ schema
2002
+ } = useSqlSchema(shardKey);
2003
+ const activeTab = tabs.find((each) => each.id === activeTabId) ?? tabs[0] ?? makeTab();
2004
+ const draft = activeTab.sql;
2005
+ const {
2006
+ activeId
2007
+ } = activeTab;
2008
+ const output = outputs[activeTab.id] ?? DEFAULT_TAB_OUTPUT;
2009
+ const {
2010
+ error,
2011
+ result
2012
+ } = output;
2013
+ const tab = output.pane;
2014
+ const patchActiveTab = (patch) => {
2015
+ setTabs((current) => current.map((each_0) => each_0.id === activeTab.id ? {
2016
+ ...each_0,
2017
+ ...patch
2018
+ } : each_0));
2019
+ };
2020
+ const setActiveOutput = (next) => {
2021
+ setOutputs((current_0) => {
2022
+ const previous = current_0[activeTab.id] ?? DEFAULT_TAB_OUTPUT;
2023
+ return {
2024
+ ...current_0,
2025
+ [activeTab.id]: {
2026
+ error: next.error,
2027
+ pane: next.pane ?? previous.pane,
2028
+ result: next.result
2029
+ }
2030
+ };
2031
+ });
2032
+ };
2033
+ const setActivePane = (pane) => {
2034
+ setOutputs((current_1) => {
2035
+ const previous_0 = current_1[activeTab.id] ?? DEFAULT_TAB_OUTPUT;
2036
+ return {
2037
+ ...current_1,
2038
+ [activeTab.id]: {
2039
+ ...previous_0,
2040
+ pane
2041
+ }
2042
+ };
2043
+ });
2044
+ };
2045
+ const setDraft = (value) => {
2046
+ patchActiveTab({
2047
+ sql: value
2048
+ });
2049
+ if (activeId !== null) {
2050
+ setQueries((current_2) => current_2.map((query) => query.id === activeId ? {
2051
+ ...query,
2052
+ sql: value
2053
+ } : query));
2054
+ }
2055
+ };
2056
+ const autocomplete = useSqlAutocomplete(schema, editorRef, setDraft);
2057
+ const {
2058
+ close: closeAutocomplete,
2059
+ commit: commitAutocomplete,
2060
+ move: moveAutocomplete,
2061
+ refresh: refreshAutocomplete,
2062
+ state: autocompleteState
2063
+ } = autocomplete;
2064
+ const onPickSuggestion = (index) => {
2065
+ moveAutocomplete(index - (autocompleteState?.active ?? 0));
2066
+ commitAutocomplete();
2067
+ };
2068
+ useEffect(() => {
2069
+ const node = editorRef.current;
2070
+ if (node !== null && node === document.activeElement) {
2071
+ refreshAutocomplete(node.value, node.selectionStart);
2072
+ }
2073
+ }, [refreshAutocomplete, schema]);
2074
+ const recordHistory = (sql) => {
2075
+ setHistory((current_3) => {
2076
+ if (current_3[0]?.sql === sql) {
2077
+ return current_3;
2078
+ }
2079
+ const next_0 = [{
2080
+ at: Date.now(),
2081
+ sql
2082
+ }, ...current_3.filter((entry) => entry.sql !== sql)];
2083
+ return next_0.slice(0, HISTORY_LIMIT);
2084
+ });
2085
+ };
2086
+ const run = async (mode) => {
2087
+ if (draft.trim() === "") {
2088
+ return;
2089
+ }
2090
+ setRunning(true);
2091
+ const sql_0 = mode === "explain" ? `EXPLAIN QUERY PLAN ${draft}` : draft;
2092
+ try {
2093
+ const next_1 = await client.query(RUN_SQL, {
2094
+ sql: sql_0
2095
+ }, callOptions(shardKey));
2096
+ setActiveOutput({
2097
+ error: null,
2098
+ pane: mode,
2099
+ result: next_1
2100
+ });
2101
+ recordShard(shardKey);
2102
+ recordHistory(sql_0);
2103
+ } catch (error_) {
2104
+ setActiveOutput({
2105
+ error: errorMessage(error_),
2106
+ pane: mode,
2107
+ result: null
2108
+ });
2109
+ } finally {
2110
+ setRunning(false);
2111
+ }
2112
+ };
2113
+ const onRun = () => {
2114
+ fireAndForget(run("results"));
2115
+ };
2116
+ const onDraftChange = (event) => {
2117
+ const {
2118
+ selectionStart,
2119
+ value: value_0
2120
+ } = event.target;
2121
+ setDraft(value_0);
2122
+ for (const table of referencedTables(value_0)) {
2123
+ probe(table);
2124
+ }
2125
+ refreshAutocomplete(value_0, selectionStart);
2126
+ };
2127
+ const onEditorSelect = (event_0) => {
2128
+ const node_0 = event_0.currentTarget;
2129
+ refreshAutocomplete(node_0.value, node_0.selectionStart);
2130
+ };
2131
+ const onEditorKeyDown = (event_1) => {
2132
+ if (autocompleteState !== null) {
2133
+ if (event_1.key === "ArrowDown") {
2134
+ event_1.preventDefault();
2135
+ moveAutocomplete(1);
2136
+ return;
2137
+ }
2138
+ if (event_1.key === "ArrowUp") {
2139
+ event_1.preventDefault();
2140
+ moveAutocomplete(-1);
2141
+ return;
2142
+ }
2143
+ if (event_1.key === "Escape") {
2144
+ event_1.preventDefault();
2145
+ closeAutocomplete();
2146
+ return;
2147
+ }
2148
+ if ((event_1.key === "Enter" || event_1.key === "Tab") && !event_1.metaKey && !event_1.ctrlKey && commitAutocomplete()) {
2149
+ event_1.preventDefault();
2150
+ return;
2151
+ }
2152
+ }
2153
+ if ((event_1.metaKey || event_1.ctrlKey) && event_1.key === "Enter") {
2154
+ event_1.preventDefault();
2155
+ fireAndForget(run(tab));
2156
+ }
2157
+ };
2158
+ const onEditorBlur = () => {
2159
+ requestAnimationFrame(() => {
2160
+ closeAutocomplete();
2161
+ });
2162
+ };
2163
+ const onEditorScroll = (event_2) => {
2164
+ if (gutterRef.current !== null) {
2165
+ gutterRef.current.scrollTop = event_2.currentTarget.scrollTop;
2166
+ }
2167
+ };
2168
+ const loadIntoActiveTab = (sql_1, savedId) => {
2169
+ setTabs((current_4) => current_4.map((each_1) => each_1.id === activeTab.id ? {
2170
+ ...each_1,
2171
+ activeId: savedId,
2172
+ sql: sql_1
2173
+ } : each_1));
2174
+ setActiveOutput({
2175
+ error: null,
2176
+ result: null
2177
+ });
2178
+ };
2179
+ const newQuery = () => {
2180
+ const query_0 = {
2181
+ id: newId("q"),
2182
+ name: t("Untitled query"),
2183
+ sql: ""
2184
+ };
2185
+ setQueries((current_5) => [query_0, ...current_5]);
2186
+ loadIntoActiveTab("", query_0.id);
2187
+ };
2188
+ const selectQuery = (id) => {
2189
+ const found = queries.find((query_1) => query_1.id === id);
2190
+ if (found !== void 0) {
2191
+ loadIntoActiveTab(found.sql, id);
2192
+ privateListRef.current?.querySelector(`[data-testid="sql-query-${id}"]`)?.scrollIntoView({
2193
+ block: "nearest"
2194
+ });
2195
+ }
2196
+ };
2197
+ const deleteQuery = (id_0) => {
2198
+ setQueries((current_6) => current_6.filter((query_2) => query_2.id !== id_0));
2199
+ setTabs((current_7) => current_7.map((each_2) => each_2.activeId === id_0 ? {
2200
+ ...each_2,
2201
+ activeId: null
2202
+ } : each_2));
2203
+ };
2204
+ const loadTemplate = (event_3) => {
2205
+ loadIntoActiveTab(event_3.currentTarget.dataset.sql ?? "", null);
2206
+ };
2207
+ const loadFromHistory = (event_4) => {
2208
+ loadIntoActiveTab(event_4.currentTarget.dataset.sql ?? "", null);
2209
+ };
2210
+ const clearHistory = () => {
2211
+ setHistory([]);
2212
+ };
2213
+ const formatDraft = () => {
2214
+ setDraft(formatSql(draft));
2215
+ };
2216
+ const addEditorTab = () => {
2217
+ if (tabs.length >= MAX_TABS) {
2218
+ return;
2219
+ }
2220
+ const fresh = makeTab();
2221
+ setTabs((current_8) => addTab(current_8, fresh));
2222
+ setActiveTabId(fresh.id);
2223
+ };
2224
+ const commitTabs = (next_2) => {
2225
+ setTabMenu(null);
2226
+ setPendingBulk(null);
2227
+ setTabs(next_2.tabs);
2228
+ setActiveTabId(next_2.activeId);
2229
+ const openIds = new Set(next_2.tabs.map((each_3) => each_3.id));
2230
+ setOutputs((current_9) => Object.fromEntries(Object.entries(current_9).filter(([key]) => openIds.has(key))));
2231
+ };
2232
+ const closeEditorTab = (id_1) => {
2233
+ commitTabs(closeTab(tabs, id_1, makeTab));
2234
+ };
2235
+ const applyBulk = (op, id_2) => {
2236
+ if (op === "others") {
2237
+ commitTabs(closeOtherTabs(tabs, id_2));
2238
+ } else if (op === "right") {
2239
+ commitTabs(closeTabsToRight(tabs, id_2, activeTabId));
2240
+ } else {
2241
+ commitTabs(closeAllTabs(makeTab));
2242
+ }
2243
+ };
2244
+ const requestBulk = (op_0) => {
2245
+ if (tabMenu === null) {
2246
+ return;
2247
+ }
2248
+ if (tabsClosedBy(op_0, tabs, tabMenu.id).some((each_4) => isDirty(each_4))) {
2249
+ setPendingBulk(op_0);
2250
+ } else {
2251
+ applyBulk(op_0, tabMenu.id);
2252
+ }
2253
+ };
2254
+ const openTabMenu = (id_3, event_5) => {
2255
+ event_5.preventDefault();
2256
+ setPendingBulk(null);
2257
+ setTabMenu({
2258
+ id: id_3,
2259
+ x: event_5.clientX,
2260
+ y: event_5.clientY
2261
+ });
2262
+ };
2263
+ const closeTabMenu = () => {
2264
+ setTabMenu(null);
2265
+ setPendingBulk(null);
2266
+ };
2267
+ const onBackdropContextMenu = (event_6) => {
2268
+ event_6.preventDefault();
2269
+ setTabMenu(null);
2270
+ setPendingBulk(null);
2271
+ };
2272
+ const onCloseOthers = () => {
2273
+ requestBulk("others");
2274
+ };
2275
+ const onCloseToRight = () => {
2276
+ requestBulk("right");
2277
+ };
2278
+ const onCloseAll = () => {
2279
+ requestBulk("all");
2280
+ };
2281
+ const confirmBulk = () => {
2282
+ if (tabMenu !== null && pendingBulk !== null) {
2283
+ applyBulk(pendingBulk, tabMenu.id);
2284
+ }
2285
+ setPendingBulk(null);
2286
+ };
2287
+ const cancelBulk = () => {
2288
+ setPendingBulk(null);
2289
+ };
2290
+ const tabMenuStyle = tabMenu === null ? void 0 : {
2291
+ left: tabMenu.x,
2292
+ top: tabMenu.y
2293
+ };
2294
+ const selectTab = (id_4) => {
2295
+ closeAutocomplete();
2296
+ setActiveTabId(id_4);
2297
+ };
2298
+ const renameTab = (id_5, name) => {
2299
+ setTabs((current_10) => current_10.map((each_5) => each_5.id === id_5 ? {
2300
+ ...each_5,
2301
+ name
2302
+ } : each_5));
2303
+ };
2304
+ const showResults = () => {
2305
+ setActivePane("results");
2306
+ };
2307
+ const showExplain = () => {
2308
+ fireAndForget(run("explain"));
2309
+ };
2310
+ const showChart = () => {
2311
+ setActivePane("chart");
2312
+ };
2313
+ const lineCount = draft.split("\n").length;
2314
+ const onSearchChange = (event_7) => {
2315
+ setSearch(event_7.target.value);
2316
+ };
2317
+ const tabClass = (selected) => `border-b-2 px-3 py-2 text-sm outline-none transition-colors ${selected ? "border-foreground font-medium text-foreground" : "border-transparent text-muted-foreground hover:text-foreground"}`;
2318
+ return /* @__PURE__ */ jsxDEV("div", {
2319
+ className: "flex h-full min-w-0",
2320
+ "data-testid": "lunora-sql-editor",
2321
+ children: [/* @__PURE__ */ jsxDEV(SqlQuerySidebar, {
2322
+ activeId,
2323
+ history,
2324
+ listRef: privateListRef,
2325
+ onClearHistory: clearHistory,
2326
+ onDelete: deleteQuery,
2327
+ onLoadHistory: loadFromHistory,
2328
+ onLoadTemplate: loadTemplate,
2329
+ onNew: newQuery,
2330
+ onSearchChange,
2331
+ onSelect: selectQuery,
2332
+ queries,
2333
+ search
2334
+ }, void 0, false), /* @__PURE__ */ jsxDEV("div", {
2335
+ className: "flex min-w-0 flex-1 flex-col",
2336
+ children: [/* @__PURE__ */ jsxDEV("div", {
2337
+ className: "flex shrink-0 items-stretch overflow-x-auto border-b border-border bg-muted/30",
2338
+ "data-testid": "sql-tab-strip",
2339
+ role: "tablist",
2340
+ children: [tabs.map((each_6) => /* @__PURE__ */ jsxDEV(TabButton, {
2341
+ active: each_6.id === activeTab.id,
2342
+ canClose: tabs.length > 1,
2343
+ onClose: closeEditorTab,
2344
+ onMenu: openTabMenu,
2345
+ onRename: renameTab,
2346
+ onSelect: selectTab,
2347
+ tab: each_6
2348
+ }, each_6.id, false)), /* @__PURE__ */ jsxDEV("button", {
2349
+ "aria-label": t("New tab"),
2350
+ className: "flex size-8 shrink-0 items-center justify-center text-muted-foreground outline-none transition-colors hover:bg-accent hover:text-foreground focus-visible:bg-accent disabled:pointer-events-none disabled:opacity-40",
2351
+ "data-testid": "sql-tab-add",
2352
+ disabled: tabs.length >= MAX_TABS,
2353
+ onClick: addEditorTab,
2354
+ title: t("New tab"),
2355
+ type: "button",
2356
+ children: /* @__PURE__ */ jsxDEV("svg", {
2357
+ "aria-hidden": "true",
2358
+ className: "size-4",
2359
+ fill: "none",
2360
+ stroke: "currentColor",
2361
+ strokeLinecap: "round",
2362
+ strokeLinejoin: "round",
2363
+ strokeWidth: 1.7,
2364
+ viewBox: "0 0 24 24",
2365
+ children: /* @__PURE__ */ jsxDEV("path", {
2366
+ d: "M12 5v14M5 12h14"
2367
+ }, void 0, false)
2368
+ }, void 0, false)
2369
+ }, void 0, false)]
2370
+ }, void 0, true), tabMenu !== null && /* @__PURE__ */ jsxDEV(Fragment, {
2371
+ children: [/* @__PURE__ */ jsxDEV("div", {
2372
+ className: "fixed inset-0 z-40",
2373
+ "data-testid": "sql-tab-menu-backdrop",
2374
+ onClick: closeTabMenu,
2375
+ onContextMenu: onBackdropContextMenu,
2376
+ role: "presentation"
2377
+ }, void 0, false), /* @__PURE__ */ jsxDEV("div", {
2378
+ className: "fixed z-50 min-w-44 rounded-md border border-border bg-popover py-1 text-popover-foreground shadow-md",
2379
+ "data-testid": "sql-tab-menu",
2380
+ role: "menu",
2381
+ style: tabMenuStyle,
2382
+ children: pendingBulk === null ? /* @__PURE__ */ jsxDEV(Fragment, {
2383
+ children: [/* @__PURE__ */ jsxDEV("button", {
2384
+ className: "flex w-full items-center px-3 py-1.5 text-start text-xs outline-none hover:bg-accent focus-visible:bg-accent disabled:pointer-events-none disabled:opacity-40",
2385
+ "data-testid": "sql-tab-menu-close-others",
2386
+ disabled: tabs.length <= 1,
2387
+ onClick: onCloseOthers,
2388
+ role: "menuitem",
2389
+ type: "button",
2390
+ children: t("Close other tabs")
2391
+ }, void 0, false), /* @__PURE__ */ jsxDEV("button", {
2392
+ className: "flex w-full items-center px-3 py-1.5 text-start text-xs outline-none hover:bg-accent focus-visible:bg-accent disabled:pointer-events-none disabled:opacity-40",
2393
+ "data-testid": "sql-tab-menu-close-right",
2394
+ disabled: tabs.findIndex((each_7) => each_7.id === tabMenu.id) >= tabs.length - 1,
2395
+ onClick: onCloseToRight,
2396
+ role: "menuitem",
2397
+ type: "button",
2398
+ children: t("Close tabs to the right")
2399
+ }, void 0, false), /* @__PURE__ */ jsxDEV("button", {
2400
+ className: "flex w-full items-center px-3 py-1.5 text-start text-xs outline-none hover:bg-accent focus-visible:bg-accent",
2401
+ "data-testid": "sql-tab-menu-close-all",
2402
+ onClick: onCloseAll,
2403
+ role: "menuitem",
2404
+ type: "button",
2405
+ children: t("Close all tabs")
2406
+ }, void 0, false)]
2407
+ }, void 0, true) : (
2408
+ // Discard confirm: the chosen bulk close would drop a tab with unsaved work.
2409
+ /* @__PURE__ */ jsxDEV("div", {
2410
+ className: "px-3 py-1.5",
2411
+ "data-testid": "sql-tab-menu-confirm",
2412
+ children: [/* @__PURE__ */ jsxDEV("p", {
2413
+ className: "pb-1.5 text-xs text-muted-foreground",
2414
+ children: t("Discard unsaved tabs?")
2415
+ }, void 0, false), /* @__PURE__ */ jsxDEV("div", {
2416
+ className: "flex items-center gap-1.5",
2417
+ children: [/* @__PURE__ */ jsxDEV("button", {
2418
+ className: "rounded px-2 py-1 text-xs font-medium text-destructive outline-none hover:bg-destructive/10 focus-visible:bg-destructive/10",
2419
+ "data-testid": "sql-tab-menu-confirm-discard",
2420
+ onClick: confirmBulk,
2421
+ type: "button",
2422
+ children: t("Discard")
2423
+ }, void 0, false), /* @__PURE__ */ jsxDEV("button", {
2424
+ className: "rounded px-2 py-1 text-xs text-muted-foreground outline-none hover:bg-accent focus-visible:bg-accent",
2425
+ "data-testid": "sql-tab-menu-confirm-cancel",
2426
+ onClick: cancelBulk,
2427
+ type: "button",
2428
+ children: t("Cancel")
2429
+ }, void 0, false)]
2430
+ }, void 0, true)]
2431
+ }, void 0, true)
2432
+ )
2433
+ }, void 0, false)]
2434
+ }, void 0, true), /* @__PURE__ */ jsxDEV("div", {
2435
+ className: "flex min-h-0 flex-1",
2436
+ children: [/* @__PURE__ */ jsxDEV("div", {
2437
+ "aria-hidden": "true",
2438
+ className: "shrink-0 select-none overflow-hidden border-e border-border bg-muted/30 py-3 text-end font-mono text-xs leading-5 text-muted-foreground/60",
2439
+ ref: gutterRef,
2440
+ style: GUTTER_STYLE,
2441
+ children: Array.from({
2442
+ length: lineCount
2443
+ }, (_, index_0) => /* @__PURE__ */ jsxDEV("div", {
2444
+ children: index_0 + 1
2445
+ }, index_0, false))
2446
+ }, void 0, false), /* @__PURE__ */ jsxDEV("div", {
2447
+ className: "relative min-w-0 flex-1",
2448
+ children: [/* @__PURE__ */ jsxDEV("textarea", {
2449
+ "aria-activedescendant": autocompleteState === null ? void 0 : `${listboxId}-opt-${autocompleteState.active.toString()}`,
2450
+ "aria-autocomplete": "list",
2451
+ "aria-controls": autocompleteState === null ? void 0 : listboxId,
2452
+ "aria-expanded": autocompleteState !== null,
2453
+ "aria-label": t("SQL query"),
2454
+ className: "size-full resize-none bg-background p-3 font-mono text-xs leading-5 outline-none",
2455
+ "data-testid": "sql-input",
2456
+ onBlur: onEditorBlur,
2457
+ onChange: onDraftChange,
2458
+ onKeyDown: onEditorKeyDown,
2459
+ onScroll: onEditorScroll,
2460
+ onSelect: onEditorSelect,
2461
+ placeholder: "SELECT * FROM …",
2462
+ ref: editorRef,
2463
+ role: "combobox",
2464
+ spellCheck: false,
2465
+ value: draft
2466
+ }, void 0, false), /* @__PURE__ */ jsxDEV(AutocompletePopover, {
2467
+ listboxId,
2468
+ onPick: onPickSuggestion,
2469
+ state: autocompleteState
2470
+ }, void 0, false)]
2471
+ }, void 0, true)]
2472
+ }, void 0, true), /* @__PURE__ */ jsxDEV("div", {
2473
+ className: "flex h-2/5 min-h-0 shrink-0 flex-col border-t border-border",
2474
+ children: [/* @__PURE__ */ jsxDEV("div", {
2475
+ className: "flex shrink-0 items-center gap-2 border-b border-border pe-2",
2476
+ children: [/* @__PURE__ */ jsxDEV("button", {
2477
+ className: tabClass(tab === "results"),
2478
+ "data-testid": "sql-tab-results",
2479
+ onClick: showResults,
2480
+ type: "button",
2481
+ children: t("Results")
2482
+ }, void 0, false), /* @__PURE__ */ jsxDEV("button", {
2483
+ className: tabClass(tab === "chart"),
2484
+ "data-testid": "sql-tab-chart",
2485
+ onClick: showChart,
2486
+ type: "button",
2487
+ children: t("Chart")
2488
+ }, void 0, false), /* @__PURE__ */ jsxDEV("button", {
2489
+ className: tabClass(tab === "explain"),
2490
+ "data-testid": "sql-tab-explain",
2491
+ onClick: showExplain,
2492
+ type: "button",
2493
+ children: t("Explain")
2494
+ }, void 0, false), /* @__PURE__ */ jsxDEV("div", {
2495
+ className: "ms-auto flex items-center gap-2",
2496
+ children: [result !== null && result.columns.length > 0 && /* @__PURE__ */ jsxDEV(ExportMenu, {
2497
+ columns: result.columns,
2498
+ name: "query-result",
2499
+ rows: result.rows
2500
+ }, void 0, false), /* @__PURE__ */ jsxDEV("button", {
2501
+ className: "inline-flex items-center rounded-md border border-border px-3 py-1.5 text-xs font-medium text-foreground outline-none transition-colors hover:bg-accent focus-visible:bg-accent disabled:pointer-events-none disabled:opacity-50",
2502
+ "data-testid": "sql-format",
2503
+ disabled: running,
2504
+ onClick: formatDraft,
2505
+ type: "button",
2506
+ children: t("Format")
2507
+ }, void 0, false), /* @__PURE__ */ jsxDEV(ShardInput, {
2508
+ onChange: setShardKey,
2509
+ testId: "sql-shard-input",
2510
+ value: shardKey
2511
+ }, void 0, false), /* @__PURE__ */ jsxDEV("button", {
2512
+ className: "inline-flex items-center gap-1.5 rounded-md bg-primary px-3 py-1.5 text-xs font-medium text-primary-foreground outline-none transition-colors hover:bg-primary/90 focus-visible:bg-primary/90 disabled:pointer-events-none disabled:opacity-50",
2513
+ "data-testid": "sql-run",
2514
+ disabled: running,
2515
+ onClick: onRun,
2516
+ type: "button",
2517
+ children: [running ? t("Running…") : t("Run"), /* @__PURE__ */ jsxDEV("kbd", {
2518
+ className: "rounded border border-primary-foreground/30 px-1 font-sans text-[10px]",
2519
+ children: "⌘↵"
2520
+ }, void 0, false)]
2521
+ }, void 0, true)]
2522
+ }, void 0, true)]
2523
+ }, void 0, true), /* @__PURE__ */ jsxDEV("div", {
2524
+ className: "min-h-0 flex-1 overflow-auto",
2525
+ children: [error !== null && /* @__PURE__ */ jsxDEV(Alert, {
2526
+ className: "m-3 font-mono text-xs",
2527
+ testId: "sql-error",
2528
+ variant: "destructive",
2529
+ children: error
2530
+ }, void 0, false), error === null && result === null && /* @__PURE__ */ jsxDEV("p", {
2531
+ className: "p-4 text-sm text-muted-foreground",
2532
+ "data-testid": "sql-empty",
2533
+ children: t("Click Run to execute your query.")
2534
+ }, void 0, false), error === null && result !== null && /* @__PURE__ */ jsxDEV("div", {
2535
+ "data-testid": "sql-result",
2536
+ children: [tab === "chart" ? /* @__PURE__ */ jsxDEV(SqlResultChart, {
2537
+ result
2538
+ }, void 0, false) : /* @__PURE__ */ jsxDEV(SqlResultTable, {
2539
+ result
2540
+ }, void 0, false), /* @__PURE__ */ jsxDEV("p", {
2541
+ className: "border-t border-border px-3 py-1.5 text-xs text-muted-foreground",
2542
+ "data-testid": "sql-count",
2543
+ children: result.truncated ? t("Showing the first {max} of {count} rows.", {
2544
+ count: result.rowCount,
2545
+ max: result.rows.length
2546
+ }) : t("{count} rows", {
2547
+ count: result.rowCount
2548
+ })
2549
+ }, void 0, false)]
2550
+ }, void 0, true)]
2551
+ }, void 0, true)]
2552
+ }, void 0, true)]
2553
+ }, void 0, true)]
2554
+ }, void 0, true);
2555
+ };
2556
+ function _temp(column) {
2557
+ return /* @__PURE__ */ jsxDEV(TableHead, {
2558
+ children: column
2559
+ }, column, false);
2560
+ }
2561
+
2562
+ export { Alert as A, SqlEditorPanel as S, SqlResultChart as a, newId as n, usePersistedList as u };