@deaquinodev/querky 0.4.7 → 0.4.9
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.
- package/dist/index.js +472 -581
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/index.tsx
|
|
4
|
-
import { useState as
|
|
4
|
+
import { useState as useState6, useEffect as useEffect5 } from "react";
|
|
5
5
|
import { parseArgs } from "util";
|
|
6
|
-
import { render, Box as
|
|
6
|
+
import { render, Box as Box7, Text as Text7 } from "ink";
|
|
7
7
|
|
|
8
8
|
// src/db/client.ts
|
|
9
9
|
import { Client } from "pg";
|
|
@@ -81,7 +81,7 @@ var SqliteDbClient = class {
|
|
|
81
81
|
const stmt = this.db.prepare(sql);
|
|
82
82
|
if (stmt.reader) {
|
|
83
83
|
const rows = stmt.all();
|
|
84
|
-
const fields = stmt.columns().map((
|
|
84
|
+
const fields = stmt.columns().map((c2) => c2.name);
|
|
85
85
|
return { fields, rows, rowCount: rows.length };
|
|
86
86
|
}
|
|
87
87
|
const info = stmt.run();
|
|
@@ -193,11 +193,11 @@ async function connectDsn(dsn) {
|
|
|
193
193
|
}
|
|
194
194
|
|
|
195
195
|
// src/ui/components/App.tsx
|
|
196
|
-
import { useState as
|
|
196
|
+
import { useState as useState4, useEffect as useEffect4, useRef as useRef2 } from "react";
|
|
197
197
|
import { writeFileSync as writeFileSync5 } from "fs";
|
|
198
198
|
import { homedir as homedir4 } from "os";
|
|
199
|
-
import { join as
|
|
200
|
-
import { Box as
|
|
199
|
+
import { join as join5 } from "path";
|
|
200
|
+
import { Box as Box5, Static, Text as Text5, useApp, useInput as useInput2, useStdin as useStdin2 } from "ink";
|
|
201
201
|
|
|
202
202
|
// src/db/query.ts
|
|
203
203
|
async function runQuery(client, sql) {
|
|
@@ -741,10 +741,10 @@ var BUILTIN_COMMAND_LIST = Object.entries(COMMANDS).map(([name, cmd]) => ({
|
|
|
741
741
|
description: cmd.description
|
|
742
742
|
}));
|
|
743
743
|
function getCompletions(partial, aliases = {}) {
|
|
744
|
-
if (partial.length === 0) return BUILTIN_COMMAND_LIST.map((
|
|
744
|
+
if (partial.length === 0) return BUILTIN_COMMAND_LIST.map((c2) => c2.name);
|
|
745
745
|
const tokenLower = partial.toLowerCase();
|
|
746
746
|
const candidates = [
|
|
747
|
-
...BUILTIN_COMMAND_LIST.map((
|
|
747
|
+
...BUILTIN_COMMAND_LIST.map((c2) => c2.name),
|
|
748
748
|
...Object.keys(aliases).filter((n) => !COMMANDS[n])
|
|
749
749
|
];
|
|
750
750
|
return candidates.map((n) => ({ n, score: fuzzyScore(tokenLower, n.toLowerCase()) })).filter(({ score }) => score >= 0).sort((a, b) => b.score - a.score).map(({ n }) => n);
|
|
@@ -831,7 +831,7 @@ async function fetchErd(client, driver) {
|
|
|
831
831
|
if (!tableMap.has(tableName)) tableMap.set(tableName, []);
|
|
832
832
|
const cols = tableMap.get(tableName);
|
|
833
833
|
const colName = String(row["column_name"] ?? "");
|
|
834
|
-
if (cols.some((
|
|
834
|
+
if (cols.some((c2) => c2.name === colName)) continue;
|
|
835
835
|
cols.push({
|
|
836
836
|
name: colName,
|
|
837
837
|
type: String(row["data_type"] ?? ""),
|
|
@@ -1050,7 +1050,7 @@ async function fetchSchema(client, driver) {
|
|
|
1050
1050
|
}
|
|
1051
1051
|
|
|
1052
1052
|
// src/ui/components/QueryInput.tsx
|
|
1053
|
-
import { useEffect as useEffect2 } from "react";
|
|
1053
|
+
import { useEffect as useEffect2, useState as useState2 } from "react";
|
|
1054
1054
|
import { Box, Text } from "ink";
|
|
1055
1055
|
|
|
1056
1056
|
// src/ui/hooks/useVimInput.ts
|
|
@@ -1523,7 +1523,7 @@ function SqlLine({ text, bg }) {
|
|
|
1523
1523
|
const tokens = tokenizeSql(text);
|
|
1524
1524
|
return /* @__PURE__ */ jsx(Fragment, { children: tokens.map((tok, i) => /* @__PURE__ */ jsx(Text, { backgroundColor: bg, color: sqlTokenColor(tok.type), children: tok.text }, i)) });
|
|
1525
1525
|
}
|
|
1526
|
-
function SqlHighlightedInput({ text, cursorAt, mode, pad
|
|
1526
|
+
function SqlHighlightedInput({ text, cursorAt, mode, pad }) {
|
|
1527
1527
|
const tokens = tokenizeSql(text);
|
|
1528
1528
|
const parts = [];
|
|
1529
1529
|
let pos = 0;
|
|
@@ -1557,7 +1557,7 @@ function SqlHighlightedInput({ text, cursorAt, mode, pad: pad2 }) {
|
|
|
1557
1557
|
parts.push(/* @__PURE__ */ jsx(Text, { backgroundColor: ACCENT, color: BG, bold: true, children: " " }, "c"));
|
|
1558
1558
|
}
|
|
1559
1559
|
}
|
|
1560
|
-
parts.push(/* @__PURE__ */ jsx(Text, { backgroundColor: BG, children:
|
|
1560
|
+
parts.push(/* @__PURE__ */ jsx(Text, { backgroundColor: BG, children: pad }, "pad"));
|
|
1561
1561
|
return /* @__PURE__ */ jsx(Fragment, { children: parts });
|
|
1562
1562
|
}
|
|
1563
1563
|
var HINTS = {
|
|
@@ -1586,7 +1586,7 @@ var HINTS = {
|
|
|
1586
1586
|
["/toggle-vim-mode", "ENABLE VIM"]
|
|
1587
1587
|
]
|
|
1588
1588
|
};
|
|
1589
|
-
var BUILTIN_DESC_MAP = Object.fromEntries(BUILTIN_COMMAND_LIST.map((
|
|
1589
|
+
var BUILTIN_DESC_MAP = Object.fromEntries(BUILTIN_COMMAND_LIST.map((c2) => [c2.name, c2.description]));
|
|
1590
1590
|
function QueryInput({ onSubmit, isLoading, onModeChange, onShellModeChange, vimEnabled = true, history = [], aliases = {}, schema }) {
|
|
1591
1591
|
function handleTab(current) {
|
|
1592
1592
|
if (!current.startsWith("/")) return null;
|
|
@@ -1620,8 +1620,13 @@ function QueryInput({ onSubmit, isLoading, onModeChange, onShellModeChange, vimE
|
|
|
1620
1620
|
const isShellMode = value.startsWith("!");
|
|
1621
1621
|
const isCommand = !isShellMode && value.startsWith("/");
|
|
1622
1622
|
const partial = isCommand ? value.slice(1) : "";
|
|
1623
|
-
const
|
|
1623
|
+
const liveSuggestions = isCommand ? getCompletions(partial, aliases) : !isShellMode && schema ? getSqlCompletions(value, cursorPos, schema) : [];
|
|
1624
1624
|
const sqlToken = isCommand ? "" : getCurrentToken(value, cursorPos);
|
|
1625
|
+
const [suggestions, setSuggestions] = useState2([]);
|
|
1626
|
+
useEffect2(() => {
|
|
1627
|
+
const t = setTimeout(() => setSuggestions(liveSuggestions), 150);
|
|
1628
|
+
return () => clearTimeout(t);
|
|
1629
|
+
}, [value, cursorPos]);
|
|
1625
1630
|
const descMap = {
|
|
1626
1631
|
...BUILTIN_DESC_MAP,
|
|
1627
1632
|
...Object.fromEntries(
|
|
@@ -1644,7 +1649,7 @@ function QueryInput({ onSubmit, isLoading, onModeChange, onShellModeChange, vimE
|
|
|
1644
1649
|
const isMultiLine = !isEmpty && !isShellMode && !isCommand && value.includes("\n");
|
|
1645
1650
|
const dOff = isShellMode ? 2 : 0;
|
|
1646
1651
|
const placeholder = "Type a SQL query\u2026";
|
|
1647
|
-
const previewResult = !isMultiLine && suggestionIndex >= 0 &&
|
|
1652
|
+
const previewResult = !isMultiLine && suggestionIndex >= 0 && liveSuggestions.length > 0 ? handleSuggestionAccept(value, cursorPos, liveSuggestions[suggestionIndex]) : null;
|
|
1648
1653
|
const renderValue = previewResult?.value ?? value;
|
|
1649
1654
|
const renderCursor = previewResult?.cursor ?? cursorPos;
|
|
1650
1655
|
const lineWidth = innerWidth - 4;
|
|
@@ -1667,6 +1672,17 @@ function QueryInput({ onSubmit, isLoading, onModeChange, onShellModeChange, vimE
|
|
|
1667
1672
|
0
|
|
1668
1673
|
);
|
|
1669
1674
|
const hintsInline = totalHintsWidth <= termWidth;
|
|
1675
|
+
const STRIP_VISIBLE = 4;
|
|
1676
|
+
const stripPrefixW = isCommand ? 1 : 0;
|
|
1677
|
+
const stripAvailW = termWidth - 2 - 3 - (STRIP_VISIBLE - 1) * 3 - STRIP_VISIBLE * stripPrefixW;
|
|
1678
|
+
const stripMaxPerItem = Math.max(6, Math.floor(stripAvailW / STRIP_VISIBLE));
|
|
1679
|
+
const hasSuggestions = !isEmpty && !isShellMode && !isMultiLine && suggestions.length > 0;
|
|
1680
|
+
const stripWinStart = hasSuggestions ? Math.min(Math.max(0, suggestionIndex - 1), Math.max(0, suggestions.length - STRIP_VISIBLE)) : 0;
|
|
1681
|
+
const stripWindow = hasSuggestions ? suggestions.slice(stripWinStart, stripWinStart + STRIP_VISIBLE) : [];
|
|
1682
|
+
const stripNavHint = hasSuggestions ? `Tab/\u2191\u2193 navigate Enter select ${suggestionIndex + 1}/${suggestions.length}` : " ";
|
|
1683
|
+
function truncateSugg(s) {
|
|
1684
|
+
return s.length > stripMaxPerItem ? s.slice(0, stripMaxPerItem - 1) + "\u2026" : s;
|
|
1685
|
+
}
|
|
1670
1686
|
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
|
|
1671
1687
|
isMultiLine ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1672
1688
|
/* @__PURE__ */ jsx(Text, { backgroundColor: BG, children: emptyLine }),
|
|
@@ -1707,131 +1723,35 @@ function QueryInput({ onSubmit, isLoading, onModeChange, onShellModeChange, vimE
|
|
|
1707
1723
|
] }),
|
|
1708
1724
|
/* @__PURE__ */ jsx(Text, { backgroundColor: BG, children: emptyLine })
|
|
1709
1725
|
] }),
|
|
1710
|
-
(
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
}
|
|
1723
|
-
|
|
1724
|
-
/* @__PURE__ */ jsx(
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
return /* @__PURE__ */ jsxs(Text, { children: [
|
|
1730
|
-
i > 0 && /* @__PURE__ */ jsx(Text, { dimColor: true, children: " " }),
|
|
1731
|
-
isCommand && /* @__PURE__ */ jsx(Text, { dimColor: !selected, color: selected ? ACCENT : void 0, children: "/" }),
|
|
1732
|
-
/* @__PURE__ */ jsx(FuzzyHighlight, { text: displayName, token: isCommand ? partial : sqlToken, selected })
|
|
1733
|
-
] }, name);
|
|
1734
|
-
}),
|
|
1735
|
-
suggestions.length > VISIBLE && /* @__PURE__ */ jsx(Text, { dimColor: true, children: " \u2026" })
|
|
1736
|
-
] }) : /* @__PURE__ */ jsx(Box, { flexDirection: hintsInline ? "row" : "column", children: allHints.map(([key, desc], i) => /* @__PURE__ */ jsxs(Text, { children: [
|
|
1737
|
-
hintsInline && i > 0 && /* @__PURE__ */ jsx(Text, { dimColor: true, children: " | " }),
|
|
1738
|
-
/* @__PURE__ */ jsx(Text, { color: ACCENT, bold: true, children: desc }),
|
|
1739
|
-
/* @__PURE__ */ jsx(Text, { dimColor: true, children: `: ${key}` })
|
|
1740
|
-
] }, desc)) }) }),
|
|
1741
|
-
/* @__PURE__ */ jsx(Box, { marginLeft: 2, children: hasSuggestions ? /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
|
|
1742
|
-
"Tab/\u2191\u2193 navigate Enter select ",
|
|
1743
|
-
suggestionIndex + 1,
|
|
1744
|
-
"/",
|
|
1745
|
-
suggestions.length
|
|
1746
|
-
] }) : /* @__PURE__ */ jsx(Text, { children: " " }) })
|
|
1747
|
-
] });
|
|
1748
|
-
})()
|
|
1726
|
+
/* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginTop: 1, children: [
|
|
1727
|
+
/* @__PURE__ */ jsx(Box, { marginLeft: 2, children: hasSuggestions ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1728
|
+
stripWindow.map((name, i) => {
|
|
1729
|
+
const realIdx = stripWinStart + i;
|
|
1730
|
+
const selected = realIdx === suggestionIndex;
|
|
1731
|
+
return /* @__PURE__ */ jsxs(Text, { children: [
|
|
1732
|
+
i > 0 && /* @__PURE__ */ jsx(Text, { dimColor: true, children: " " }),
|
|
1733
|
+
isCommand && /* @__PURE__ */ jsx(Text, { dimColor: !selected, color: selected ? ACCENT : void 0, children: "/" }),
|
|
1734
|
+
/* @__PURE__ */ jsx(FuzzyHighlight, { text: truncateSugg(name), token: isCommand ? partial : sqlToken, selected })
|
|
1735
|
+
] }, name);
|
|
1736
|
+
}),
|
|
1737
|
+
suggestions.length > STRIP_VISIBLE && /* @__PURE__ */ jsx(Text, { dimColor: true, children: " \u2026" })
|
|
1738
|
+
] }) : /* @__PURE__ */ jsx(Box, { flexDirection: hintsInline ? "row" : "column", children: allHints.map(([key, desc], i) => /* @__PURE__ */ jsxs(Text, { children: [
|
|
1739
|
+
hintsInline && i > 0 && /* @__PURE__ */ jsx(Text, { dimColor: true, children: " | " }),
|
|
1740
|
+
/* @__PURE__ */ jsx(Text, { color: ACCENT, bold: true, children: desc }),
|
|
1741
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: `: ${key}` })
|
|
1742
|
+
] }, desc)) }) }),
|
|
1743
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: stripNavHint })
|
|
1744
|
+
] })
|
|
1749
1745
|
] });
|
|
1750
1746
|
}
|
|
1751
1747
|
|
|
1752
1748
|
// src/ui/components/QueryResult.tsx
|
|
1753
|
-
import { useState as
|
|
1749
|
+
import { useState as useState3, useEffect as useEffect3 } from "react";
|
|
1754
1750
|
import { Box as Box3, Text as Text3 } from "ink";
|
|
1755
1751
|
|
|
1756
1752
|
// src/ui/components/Table.tsx
|
|
1757
1753
|
import { Box as Box2, Text as Text2 } from "ink";
|
|
1758
1754
|
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
1759
|
-
var COL_PAD = 1;
|
|
1760
|
-
var INDIGO = "#818cf8";
|
|
1761
|
-
var BORDER = "white";
|
|
1762
|
-
var NULL_COLOR = "#6366f1";
|
|
1763
|
-
var NULL_MARKER = "\u2205";
|
|
1764
|
-
function isNull(val) {
|
|
1765
|
-
return val === null || val === void 0;
|
|
1766
|
-
}
|
|
1767
|
-
function cellValue(val) {
|
|
1768
|
-
if (isNull(val)) return NULL_MARKER;
|
|
1769
|
-
if (val instanceof Date) return val.toISOString();
|
|
1770
|
-
if (typeof val === "object") return JSON.stringify(val);
|
|
1771
|
-
return String(val);
|
|
1772
|
-
}
|
|
1773
|
-
function colWidths(columns, rows) {
|
|
1774
|
-
return columns.map((col) => {
|
|
1775
|
-
const dataMax = rows.reduce(
|
|
1776
|
-
(max, row) => Math.max(max, cellValue(row[col]).length),
|
|
1777
|
-
0
|
|
1778
|
-
);
|
|
1779
|
-
return Math.max(col.length, dataMax);
|
|
1780
|
-
});
|
|
1781
|
-
}
|
|
1782
|
-
function pad(str, width) {
|
|
1783
|
-
return str.slice(0, width).padEnd(width);
|
|
1784
|
-
}
|
|
1785
|
-
function hline(widths, left, mid, right) {
|
|
1786
|
-
const segments = widths.map((w) => "\u2500".repeat(w + COL_PAD * 2));
|
|
1787
|
-
return left + segments.join(mid) + right;
|
|
1788
|
-
}
|
|
1789
|
-
function ExpandedTable({ columns, rows }) {
|
|
1790
|
-
const keyWidth = columns.reduce((max, col) => Math.max(max, col.length), 0);
|
|
1791
|
-
return /* @__PURE__ */ jsx2(Box2, { flexDirection: "column", children: rows.map((row, i) => /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", marginBottom: i < rows.length - 1 ? 1 : 0, children: [
|
|
1792
|
-
/* @__PURE__ */ jsx2(Text2, { color: BORDER, children: `\u2500[ Record ${i + 1} ]${"\u2500".repeat(Math.max(0, keyWidth + 14 - String(i + 1).length))}` }),
|
|
1793
|
-
columns.map((col) => /* @__PURE__ */ jsxs2(Box2, { children: [
|
|
1794
|
-
/* @__PURE__ */ jsx2(Text2, { color: INDIGO, bold: true, children: col.padEnd(keyWidth) }),
|
|
1795
|
-
/* @__PURE__ */ jsx2(Text2, { color: BORDER, children: " \u2502 " }),
|
|
1796
|
-
isNull(row[col]) ? /* @__PURE__ */ jsx2(Text2, { color: NULL_COLOR, dimColor: true, children: NULL_MARKER }) : /* @__PURE__ */ jsx2(Text2, { children: cellValue(row[col]) })
|
|
1797
|
-
] }, col))
|
|
1798
|
-
] }, i)) });
|
|
1799
|
-
}
|
|
1800
|
-
function Table({ columns, rows, expanded = false }) {
|
|
1801
|
-
if (expanded) return /* @__PURE__ */ jsx2(ExpandedTable, { columns, rows });
|
|
1802
|
-
const widths = colWidths(columns, rows);
|
|
1803
|
-
const topLine = hline(widths, "\u256D", "\u252C", "\u256E");
|
|
1804
|
-
const midLine = hline(widths, "\u251C", "\u253C", "\u2524");
|
|
1805
|
-
const botLine = hline(widths, "\u2570", "\u2534", "\u256F");
|
|
1806
|
-
function renderHeaderRow(cols) {
|
|
1807
|
-
return /* @__PURE__ */ jsxs2(Box2, { children: [
|
|
1808
|
-
/* @__PURE__ */ jsx2(Text2, { color: BORDER, children: "\u2502" }),
|
|
1809
|
-
cols.map((v, i) => /* @__PURE__ */ jsxs2(Box2, { children: [
|
|
1810
|
-
/* @__PURE__ */ jsx2(Text2, { color: INDIGO, bold: true, children: " ".repeat(COL_PAD) + pad(v, widths[i]) + " ".repeat(COL_PAD) }),
|
|
1811
|
-
/* @__PURE__ */ jsx2(Text2, { color: BORDER, children: "\u2502" })
|
|
1812
|
-
] }, i))
|
|
1813
|
-
] });
|
|
1814
|
-
}
|
|
1815
|
-
function renderDataRow(row) {
|
|
1816
|
-
return /* @__PURE__ */ jsxs2(Box2, { children: [
|
|
1817
|
-
/* @__PURE__ */ jsx2(Text2, { color: BORDER, children: "\u2502" }),
|
|
1818
|
-
columns.map((col, i) => /* @__PURE__ */ jsxs2(Box2, { children: [
|
|
1819
|
-
isNull(row[col]) ? /* @__PURE__ */ jsx2(Text2, { color: NULL_COLOR, dimColor: true, children: " ".repeat(COL_PAD) + pad(NULL_MARKER, widths[i]) + " ".repeat(COL_PAD) }) : /* @__PURE__ */ jsx2(Text2, { children: " ".repeat(COL_PAD) + pad(cellValue(row[col]), widths[i]) + " ".repeat(COL_PAD) }),
|
|
1820
|
-
/* @__PURE__ */ jsx2(Text2, { color: BORDER, children: "\u2502" })
|
|
1821
|
-
] }, i))
|
|
1822
|
-
] });
|
|
1823
|
-
}
|
|
1824
|
-
return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", children: [
|
|
1825
|
-
/* @__PURE__ */ jsx2(Text2, { color: BORDER, children: topLine }),
|
|
1826
|
-
renderHeaderRow(columns),
|
|
1827
|
-
/* @__PURE__ */ jsx2(Text2, { color: BORDER, children: midLine }),
|
|
1828
|
-
rows.map((row, i) => /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", children: [
|
|
1829
|
-
renderDataRow(row),
|
|
1830
|
-
i < rows.length - 1 && /* @__PURE__ */ jsx2(Text2, { color: BORDER, children: midLine })
|
|
1831
|
-
] }, i)),
|
|
1832
|
-
/* @__PURE__ */ jsx2(Text2, { color: BORDER, children: botLine })
|
|
1833
|
-
] });
|
|
1834
|
-
}
|
|
1835
1755
|
|
|
1836
1756
|
// src/ui/components/QueryResult.tsx
|
|
1837
1757
|
import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
@@ -1843,90 +1763,11 @@ function ErrorBox({ message }) {
|
|
|
1843
1763
|
line
|
|
1844
1764
|
] }, i)) });
|
|
1845
1765
|
}
|
|
1846
|
-
function formatDuration(ms) {
|
|
1847
|
-
if (ms < 1e3) return `${ms}ms`;
|
|
1848
|
-
return `${(ms / 1e3).toFixed(2)}s`;
|
|
1849
|
-
}
|
|
1850
|
-
function useTerminalWidth() {
|
|
1851
|
-
const [width, setWidth] = useState2(process.stdout.columns ?? 80);
|
|
1852
|
-
useEffect3(() => {
|
|
1853
|
-
const handler = () => setWidth(process.stdout.columns ?? 80);
|
|
1854
|
-
process.stdout.on("resize", handler);
|
|
1855
|
-
return () => {
|
|
1856
|
-
process.stdout.off("resize", handler);
|
|
1857
|
-
};
|
|
1858
|
-
}, []);
|
|
1859
|
-
return width;
|
|
1860
|
-
}
|
|
1861
|
-
function QueryResult({ state, elapsed, page, pageSize }) {
|
|
1862
|
-
const termWidth = useTerminalWidth();
|
|
1863
|
-
if (state.status === "idle" || state.status === "running") return null;
|
|
1864
|
-
const timing = elapsed !== null ? ` \xB7 ${formatDuration(elapsed)}` : "";
|
|
1865
|
-
if (state.status === "error") {
|
|
1866
|
-
return /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", children: [
|
|
1867
|
-
/* @__PURE__ */ jsx3(ErrorBox, { message: state.message }),
|
|
1868
|
-
elapsed !== null && /* @__PURE__ */ jsx3(Text3, { color: theme.accent, dimColor: true, children: formatDuration(elapsed) })
|
|
1869
|
-
] });
|
|
1870
|
-
}
|
|
1871
|
-
const { result } = state;
|
|
1872
|
-
const columns = result.fields;
|
|
1873
|
-
if (result.rows.length === 0) {
|
|
1874
|
-
return /* @__PURE__ */ jsx3(Box3, { children: /* @__PURE__ */ jsxs3(Text3, { color: theme.accent, children: [
|
|
1875
|
-
"No rows returned (",
|
|
1876
|
-
result.rowCount ?? 0,
|
|
1877
|
-
" affected)",
|
|
1878
|
-
timing
|
|
1879
|
-
] }) });
|
|
1880
|
-
}
|
|
1881
|
-
const totalRows = result.rows.length;
|
|
1882
|
-
const totalPages = Math.ceil(totalRows / pageSize);
|
|
1883
|
-
const currentPage = Math.min(page, totalPages - 1);
|
|
1884
|
-
const start = currentPage * pageSize;
|
|
1885
|
-
const displayRows = result.rows.slice(start, start + pageSize);
|
|
1886
|
-
const isPaged = totalRows > pageSize;
|
|
1887
|
-
const widths = colWidths(columns, displayRows);
|
|
1888
|
-
const tableWidth = 1 + widths.reduce((sum, w) => sum + w + 3, 0);
|
|
1889
|
-
const expanded = tableWidth > termWidth;
|
|
1890
|
-
return /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", children: [
|
|
1891
|
-
/* @__PURE__ */ jsx3(Table, { columns, rows: displayRows, expanded }),
|
|
1892
|
-
/* @__PURE__ */ jsxs3(Box3, { marginTop: 1, flexDirection: "column", children: [
|
|
1893
|
-
isPaged && /* @__PURE__ */ jsxs3(Text3, { color: theme.warning, children: [
|
|
1894
|
-
"Page ",
|
|
1895
|
-
currentPage + 1,
|
|
1896
|
-
" of ",
|
|
1897
|
-
totalPages,
|
|
1898
|
-
" \xB7 showing rows ",
|
|
1899
|
-
start + 1,
|
|
1900
|
-
"\u2013",
|
|
1901
|
-
start + displayRows.length,
|
|
1902
|
-
" of ",
|
|
1903
|
-
totalRows,
|
|
1904
|
-
currentPage > 0 ? " /prev" : "",
|
|
1905
|
-
currentPage < totalPages - 1 ? " /next" : ""
|
|
1906
|
-
] }),
|
|
1907
|
-
/* @__PURE__ */ jsxs3(Text3, { color: theme.accent, children: [
|
|
1908
|
-
displayRows.length,
|
|
1909
|
-
" row",
|
|
1910
|
-
displayRows.length !== 1 ? "s" : "",
|
|
1911
|
-
timing,
|
|
1912
|
-
expanded ? " [expanded]" : ""
|
|
1913
|
-
] })
|
|
1914
|
-
] })
|
|
1915
|
-
] });
|
|
1916
|
-
}
|
|
1917
1766
|
|
|
1918
1767
|
// src/ui/components/Banner.tsx
|
|
1919
1768
|
import { Box as Box4, Text as Text4 } from "ink";
|
|
1920
|
-
import { readFileSync as readFileSync5 } from "fs";
|
|
1921
|
-
import { fileURLToPath } from "url";
|
|
1922
|
-
import { dirname, join as join5 } from "path";
|
|
1923
1769
|
import { Fragment as Fragment2, jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
1924
|
-
var
|
|
1925
|
-
var pkg = { version: "unknown" };
|
|
1926
|
-
try {
|
|
1927
|
-
pkg = JSON.parse(readFileSync5(join5(__dirname, "../../../package.json"), "utf8"));
|
|
1928
|
-
} catch {
|
|
1929
|
-
}
|
|
1770
|
+
var version = true ? "0.4.9" : process.env.npm_package_version ?? "unknown";
|
|
1930
1771
|
var LOGO = [
|
|
1931
1772
|
" \u2597\u2584\u2584\u2584\u2584\u2584\u2596",
|
|
1932
1773
|
"\u2590\u2591 \u25CF \u25CF \u2591\u258C",
|
|
@@ -1950,7 +1791,7 @@ function Banner({ connectionState }) {
|
|
|
1950
1791
|
"Querky ",
|
|
1951
1792
|
/* @__PURE__ */ jsxs4(Text4, { dimColor: true, children: [
|
|
1952
1793
|
"v",
|
|
1953
|
-
|
|
1794
|
+
version
|
|
1954
1795
|
] })
|
|
1955
1796
|
] }),
|
|
1956
1797
|
/* @__PURE__ */ jsx4(Text4, { children: " " }),
|
|
@@ -1975,56 +1816,40 @@ function Banner({ connectionState }) {
|
|
|
1975
1816
|
] });
|
|
1976
1817
|
}
|
|
1977
1818
|
|
|
1978
|
-
// src/ui/
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
var
|
|
1982
|
-
function
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
] }, entry.name))
|
|
2013
|
-
] }, group.category)),
|
|
2014
|
-
/* @__PURE__ */ jsx5(Box5, { marginTop: 1, children: /* @__PURE__ */ jsxs5(Text5, { dimColor: true, children: [
|
|
2015
|
-
"/help ",
|
|
2016
|
-
"<command>",
|
|
2017
|
-
" for more details."
|
|
2018
|
-
] }) })
|
|
2019
|
-
] });
|
|
2020
|
-
}
|
|
2021
|
-
return null;
|
|
2022
|
-
}
|
|
2023
|
-
|
|
2024
|
-
// src/ui/components/ErdView.tsx
|
|
2025
|
-
import { Box as Box6, Text as Text6 } from "ink";
|
|
2026
|
-
import { Fragment as Fragment3, jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
2027
|
-
var TABLE_COLORS = [
|
|
1819
|
+
// src/ui/format.ts
|
|
1820
|
+
var R = "\x1B[0m";
|
|
1821
|
+
var BOLD = "\x1B[1m";
|
|
1822
|
+
var DIM = "\x1B[2m";
|
|
1823
|
+
function fg(hex) {
|
|
1824
|
+
const r = parseInt(hex.slice(1, 3), 16);
|
|
1825
|
+
const g = parseInt(hex.slice(3, 5), 16);
|
|
1826
|
+
const b = parseInt(hex.slice(5, 7), 16);
|
|
1827
|
+
return `\x1B[38;2;${r};${g};${b}m`;
|
|
1828
|
+
}
|
|
1829
|
+
function c(hex, s) {
|
|
1830
|
+
return fg(hex) + s + R;
|
|
1831
|
+
}
|
|
1832
|
+
function bold(s) {
|
|
1833
|
+
return BOLD + s + R;
|
|
1834
|
+
}
|
|
1835
|
+
function dim(s) {
|
|
1836
|
+
return DIM + s + R;
|
|
1837
|
+
}
|
|
1838
|
+
function cbold(hex, s) {
|
|
1839
|
+
return fg(hex) + BOLD + s + R;
|
|
1840
|
+
}
|
|
1841
|
+
function cdim(hex, s) {
|
|
1842
|
+
return fg(hex) + DIM + s + R;
|
|
1843
|
+
}
|
|
1844
|
+
var ACCENT2 = "#818cf8";
|
|
1845
|
+
var HEADER_COL = "#9ca3af";
|
|
1846
|
+
var NULL_COL = "#6366f1";
|
|
1847
|
+
var INDIGO = "#818cf8";
|
|
1848
|
+
var AI_COL = "#a5b4fc";
|
|
1849
|
+
var ERROR_COL = "#ff4444";
|
|
1850
|
+
var WARN_COL = "#f59e0b";
|
|
1851
|
+
var ERD_BORDER = "#4b5563";
|
|
1852
|
+
var ERD_PALETTE = [
|
|
2028
1853
|
"#f472b6",
|
|
2029
1854
|
"#34d399",
|
|
2030
1855
|
"#fb923c",
|
|
@@ -2034,30 +1859,179 @@ var TABLE_COLORS = [
|
|
|
2034
1859
|
"#fbbf24",
|
|
2035
1860
|
"#2dd4bf"
|
|
2036
1861
|
];
|
|
2037
|
-
var
|
|
2038
|
-
var
|
|
2039
|
-
var
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
);
|
|
2052
|
-
|
|
2053
|
-
|
|
1862
|
+
var NULL_MARKER = "\u2205";
|
|
1863
|
+
var COL_PAD = 1;
|
|
1864
|
+
var SP = " ".repeat(COL_PAD);
|
|
1865
|
+
function fmtHeader(label, query) {
|
|
1866
|
+
const dot = cbold(ACCENT2, "\u25CF ");
|
|
1867
|
+
if (query.includes("\n")) {
|
|
1868
|
+
const lines = query.split("\n").map((l) => cbold(HEADER_COL, " " + l));
|
|
1869
|
+
return dot + cbold(HEADER_COL, label + ":") + "\n" + lines.join("\n");
|
|
1870
|
+
}
|
|
1871
|
+
return dot + cbold(HEADER_COL, `${label}(${query})`);
|
|
1872
|
+
}
|
|
1873
|
+
function fmtError(message) {
|
|
1874
|
+
return message.split("\n").map(
|
|
1875
|
+
(line, i) => cbold(ERROR_COL, (i === 0 ? "\u2717 " : " ") + line)
|
|
1876
|
+
).join("\n");
|
|
1877
|
+
}
|
|
1878
|
+
function fmtOk(text) {
|
|
1879
|
+
return c(ACCENT2, "\u2713 ") + text;
|
|
1880
|
+
}
|
|
1881
|
+
function isNull(v) {
|
|
1882
|
+
return v === null || v === void 0;
|
|
1883
|
+
}
|
|
1884
|
+
function cellStr(v) {
|
|
1885
|
+
if (isNull(v)) return NULL_MARKER;
|
|
1886
|
+
if (v instanceof Date) return v.toISOString();
|
|
1887
|
+
if (typeof v === "object") return JSON.stringify(v);
|
|
1888
|
+
return String(v);
|
|
1889
|
+
}
|
|
1890
|
+
function colWidths2(columns, rows) {
|
|
1891
|
+
return columns.map((col) => {
|
|
1892
|
+
const dataMax = rows.reduce((mx, row) => Math.max(mx, cellStr(row[col]).length), 0);
|
|
1893
|
+
return Math.max(col.length, dataMax);
|
|
1894
|
+
});
|
|
1895
|
+
}
|
|
1896
|
+
function hline(widths, l, m, r) {
|
|
1897
|
+
return l + widths.map((w) => "\u2500".repeat(w + COL_PAD * 2)).join(m) + r;
|
|
1898
|
+
}
|
|
1899
|
+
function fmtTable(columns, rows) {
|
|
1900
|
+
const widths = colWidths2(columns, rows);
|
|
1901
|
+
const top = hline(widths, "\u256D", "\u252C", "\u256E");
|
|
1902
|
+
const mid = hline(widths, "\u251C", "\u253C", "\u2524");
|
|
1903
|
+
const bot = hline(widths, "\u2570", "\u2534", "\u256F");
|
|
1904
|
+
function headerRow() {
|
|
1905
|
+
const cells = columns.map(
|
|
1906
|
+
(col, i) => cbold(INDIGO, SP + col.slice(0, widths[i]).padEnd(widths[i]) + SP)
|
|
1907
|
+
);
|
|
1908
|
+
return "\u2502" + cells.join("\u2502") + "\u2502";
|
|
1909
|
+
}
|
|
1910
|
+
function dataRow(row) {
|
|
1911
|
+
const cells = columns.map((col, i) => {
|
|
1912
|
+
const v = cellStr(row[col]);
|
|
1913
|
+
const cell = SP + v.slice(0, widths[i]).padEnd(widths[i]) + SP;
|
|
1914
|
+
return isNull(row[col]) ? cdim(NULL_COL, cell) : cell;
|
|
1915
|
+
});
|
|
1916
|
+
return "\u2502" + cells.join("\u2502") + "\u2502";
|
|
1917
|
+
}
|
|
1918
|
+
const out = [top, headerRow(), mid];
|
|
1919
|
+
rows.forEach((row, i) => {
|
|
1920
|
+
out.push(dataRow(row));
|
|
1921
|
+
if (i < rows.length - 1) out.push(mid);
|
|
1922
|
+
});
|
|
1923
|
+
out.push(bot);
|
|
1924
|
+
return out.join("\n");
|
|
1925
|
+
}
|
|
1926
|
+
function fmtExpanded(columns, rows) {
|
|
1927
|
+
const kw = columns.reduce((mx, c2) => Math.max(mx, c2.length), 0);
|
|
1928
|
+
const separator = (i) => "\u2500[ Record " + (i + 1) + " ]" + "\u2500".repeat(Math.max(0, kw + 14 - String(i + 1).length));
|
|
1929
|
+
const out = [];
|
|
1930
|
+
rows.forEach((row, i) => {
|
|
1931
|
+
if (i > 0) out.push("");
|
|
1932
|
+
out.push(dim(separator(i)));
|
|
1933
|
+
for (const col of columns) {
|
|
1934
|
+
const val = isNull(row[col]) ? cdim(NULL_COL, NULL_MARKER) : cellStr(row[col]);
|
|
1935
|
+
out.push(cbold(INDIGO, col.padEnd(kw)) + dim(" \u2502 ") + val);
|
|
1936
|
+
}
|
|
1937
|
+
});
|
|
1938
|
+
return out.join("\n");
|
|
1939
|
+
}
|
|
1940
|
+
function fmtDuration(ms) {
|
|
1941
|
+
return ms < 1e3 ? `${ms}ms` : `${(ms / 1e3).toFixed(2)}s`;
|
|
1942
|
+
}
|
|
1943
|
+
function fmtQueryResult(state, elapsed, page, pageSize) {
|
|
1944
|
+
if (state.status === "idle" || state.status === "running") return "";
|
|
1945
|
+
const timing = elapsed !== null ? ` \xB7 ${fmtDuration(elapsed)}` : "";
|
|
1946
|
+
if (state.status === "error") {
|
|
1947
|
+
const t = elapsed !== null ? "\n" + cdim(ACCENT2, fmtDuration(elapsed)) : "";
|
|
1948
|
+
return fmtError(state.message) + t;
|
|
1949
|
+
}
|
|
1950
|
+
const { result } = state;
|
|
1951
|
+
const { fields, rows } = result;
|
|
1952
|
+
if (rows.length === 0) {
|
|
1953
|
+
return c(ACCENT2, `No rows returned (${result.rowCount ?? 0} affected)${timing}`);
|
|
1954
|
+
}
|
|
1955
|
+
const totalRows = rows.length;
|
|
1956
|
+
const totalPages = Math.ceil(totalRows / pageSize);
|
|
1957
|
+
const currentPage = Math.min(page, totalPages - 1);
|
|
1958
|
+
const start = currentPage * pageSize;
|
|
1959
|
+
const displayRows = rows.slice(start, start + pageSize);
|
|
1960
|
+
const isPaged = totalRows > pageSize;
|
|
1961
|
+
const termWidth = process.stdout.columns ?? 80;
|
|
1962
|
+
const widths = colWidths2(fields, displayRows);
|
|
1963
|
+
const tableWidth = 1 + widths.reduce((sum, w) => sum + w + 3, 0);
|
|
1964
|
+
const expanded = tableWidth > termWidth;
|
|
1965
|
+
const table = expanded ? fmtExpanded(fields, displayRows) : fmtTable(fields, displayRows);
|
|
1966
|
+
const lines = [table];
|
|
1967
|
+
if (isPaged) {
|
|
1968
|
+
const nav = (currentPage > 0 ? " /prev" : "") + (currentPage < totalPages - 1 ? " /next" : "");
|
|
1969
|
+
lines.push(c(WARN_COL, `Page ${currentPage + 1} of ${totalPages} \xB7 rows ${start + 1}\u2013${start + displayRows.length} of ${totalRows}${nav}`));
|
|
1970
|
+
}
|
|
1971
|
+
lines.push(c(ACCENT2, `${displayRows.length} row${displayRows.length !== 1 ? "s" : ""}${timing}${expanded ? " [expanded]" : ""}`));
|
|
1972
|
+
return lines.join("\n");
|
|
2054
1973
|
}
|
|
2055
|
-
|
|
1974
|
+
var USAGE_WIDTH = 28;
|
|
1975
|
+
function fmtHelp(data) {
|
|
1976
|
+
if (data.mode === "detail" && data.entry) {
|
|
1977
|
+
const { usage, description, psqlAlias, detail, example, examples } = data.entry;
|
|
1978
|
+
const lines = [];
|
|
1979
|
+
lines.push(bold(usage) + (psqlAlias ? " " + dim(psqlAlias) : ""));
|
|
1980
|
+
lines.push("");
|
|
1981
|
+
lines.push(description);
|
|
1982
|
+
if (detail) {
|
|
1983
|
+
lines.push("");
|
|
1984
|
+
lines.push(dim(detail));
|
|
1985
|
+
}
|
|
1986
|
+
if (examples && examples.length > 0) {
|
|
1987
|
+
lines.push("");
|
|
1988
|
+
lines.push(dim("Examples:"));
|
|
1989
|
+
for (const ex of examples) lines.push(" " + c(ACCENT2, ex));
|
|
1990
|
+
} else if (example) {
|
|
1991
|
+
lines.push("");
|
|
1992
|
+
lines.push(dim("Example: ") + c(ACCENT2, example));
|
|
1993
|
+
}
|
|
1994
|
+
return lines.join("\n");
|
|
1995
|
+
}
|
|
1996
|
+
if (data.mode === "list" && data.groups) {
|
|
1997
|
+
const lines = [];
|
|
1998
|
+
data.groups.forEach((group, gi) => {
|
|
1999
|
+
if (gi > 0) lines.push("");
|
|
2000
|
+
lines.push(cbold(ACCENT2, group.category));
|
|
2001
|
+
for (const entry of group.entries) {
|
|
2002
|
+
const alias = entry.psqlAlias ? dim(" " + entry.psqlAlias) : "";
|
|
2003
|
+
lines.push(" " + entry.usage.padEnd(USAGE_WIDTH) + dim(entry.description) + alias);
|
|
2004
|
+
}
|
|
2005
|
+
});
|
|
2006
|
+
lines.push("");
|
|
2007
|
+
lines.push(dim("/help <command> for more details."));
|
|
2008
|
+
return lines.join("\n");
|
|
2009
|
+
}
|
|
2010
|
+
return "";
|
|
2011
|
+
}
|
|
2012
|
+
function fmtErd(data) {
|
|
2013
|
+
if (data.tables.length === 0) return dim("No tables found in the current schema.");
|
|
2014
|
+
const termW = process.stdout.columns ?? 80;
|
|
2015
|
+
const FK_PREFIX = "FK \u2192 ";
|
|
2016
|
+
const PAD = 1;
|
|
2017
|
+
const GAP = 2;
|
|
2018
|
+
const colorMap = new Map(
|
|
2019
|
+
data.tables.map((t, i) => [t.name, ERD_PALETTE[i % ERD_PALETTE.length]])
|
|
2020
|
+
);
|
|
2021
|
+
function metrics(t) {
|
|
2022
|
+
const nameW = Math.max(2, ...t.columns.map((c2) => c2.name.length));
|
|
2023
|
+
const typeW = Math.max(4, ...t.columns.map((c2) => c2.type.length));
|
|
2024
|
+
const keyW = Math.max(2, ...t.columns.map(
|
|
2025
|
+
(c2) => c2.isPk ? 2 : c2.fkTable ? FK_PREFIX.length + c2.fkTable.length : 0
|
|
2026
|
+
));
|
|
2027
|
+
const totalW = 4 + 3 * PAD * 2 + nameW + typeW + keyW;
|
|
2028
|
+
return { nameW, typeW, keyW, totalW };
|
|
2029
|
+
}
|
|
2030
|
+
const allMetrics = data.tables.map(metrics);
|
|
2056
2031
|
const rows = [];
|
|
2057
|
-
let row = [];
|
|
2058
|
-
let
|
|
2059
|
-
|
|
2060
|
-
const w = metrics[i].totalW;
|
|
2032
|
+
let row = [], usedW = 0;
|
|
2033
|
+
for (let i = 0; i < allMetrics.length; i++) {
|
|
2034
|
+
const w = allMetrics[i].totalW;
|
|
2061
2035
|
if (row.length === 0) {
|
|
2062
2036
|
row.push(i);
|
|
2063
2037
|
usedW = w;
|
|
@@ -2071,120 +2045,113 @@ function groupIntoRows(metrics, termW) {
|
|
|
2071
2045
|
}
|
|
2072
2046
|
}
|
|
2073
2047
|
if (row.length > 0) rows.push(row);
|
|
2074
|
-
|
|
2075
|
-
}
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
sp,
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
p(col.fkTable, keyW - FK_PREFIX.length),
|
|
2121
|
-
sp
|
|
2122
|
-
] })
|
|
2123
|
-
] }) : /* @__PURE__ */ jsxs6(Text6, { children: [
|
|
2124
|
-
sp,
|
|
2125
|
-
" ".repeat(keyW),
|
|
2126
|
-
sp
|
|
2127
|
-
] }),
|
|
2128
|
-
/* @__PURE__ */ jsx6(Text6, { color: BORDER2, children: "\u2502" })
|
|
2129
|
-
] }, i)),
|
|
2130
|
-
/* @__PURE__ */ jsx6(Text6, { color: BORDER2, children: bot })
|
|
2131
|
-
] });
|
|
2048
|
+
function renderTable(t, m, color) {
|
|
2049
|
+
const { nameW, typeW, keyW, totalW } = m;
|
|
2050
|
+
const sp = " ".repeat(PAD);
|
|
2051
|
+
const p = (s, w) => s.slice(0, w).padEnd(w);
|
|
2052
|
+
const bdr = (s) => c(ERD_BORDER, s);
|
|
2053
|
+
const tc = (s) => cbold(color, s);
|
|
2054
|
+
const top = bdr("\u256D" + "\u2500".repeat(totalW - 2) + "\u256E");
|
|
2055
|
+
const headerW = totalW - 4;
|
|
2056
|
+
const headerLine = bdr("\u2502") + tc(sp + p(t.name, headerW) + sp) + bdr("\u2502");
|
|
2057
|
+
const sep = bdr("\u251C" + "\u2500".repeat(nameW + PAD * 2) + "\u252C" + "\u2500".repeat(typeW + PAD * 2) + "\u252C" + "\u2500".repeat(keyW + PAD * 2) + "\u2524");
|
|
2058
|
+
const bot = bdr("\u2570" + "\u2500".repeat(nameW + PAD * 2) + "\u2534" + "\u2500".repeat(typeW + PAD * 2) + "\u2534" + "\u2500".repeat(keyW + PAD * 2) + "\u256F");
|
|
2059
|
+
const out = [top, headerLine, sep];
|
|
2060
|
+
for (const col of t.columns) {
|
|
2061
|
+
let keyPart;
|
|
2062
|
+
if (col.isPk) {
|
|
2063
|
+
keyPart = sp + bold(p("PK", keyW)) + sp;
|
|
2064
|
+
} else if (col.fkTable) {
|
|
2065
|
+
const fkColor = colorMap.get(col.fkTable) ?? color;
|
|
2066
|
+
keyPart = sp + dim(FK_PREFIX) + c(fkColor, p(col.fkTable, keyW - FK_PREFIX.length)) + sp;
|
|
2067
|
+
} else {
|
|
2068
|
+
keyPart = sp + " ".repeat(keyW) + sp;
|
|
2069
|
+
}
|
|
2070
|
+
out.push(
|
|
2071
|
+
bdr("\u2502") + sp + p(col.name, nameW) + sp + bdr("\u2502") + dim(sp + p(col.type, typeW) + sp) + bdr("\u2502") + keyPart + bdr("\u2502")
|
|
2072
|
+
);
|
|
2073
|
+
}
|
|
2074
|
+
out.push(bot);
|
|
2075
|
+
return out;
|
|
2076
|
+
}
|
|
2077
|
+
const output = [];
|
|
2078
|
+
for (const tableRow of rows) {
|
|
2079
|
+
const blocks = tableRow.map((ti) => {
|
|
2080
|
+
const t = data.tables[ti];
|
|
2081
|
+
return renderTable(t, allMetrics[ti], colorMap.get(t.name) ?? ERD_PALETTE[0]);
|
|
2082
|
+
});
|
|
2083
|
+
const height = Math.max(...blocks.map((b) => b.length));
|
|
2084
|
+
for (let line = 0; line < height; line++) {
|
|
2085
|
+
const parts = blocks.map((b, j) => {
|
|
2086
|
+
const l = b[line] ?? " ".repeat(allMetrics[tableRow[j]].totalW);
|
|
2087
|
+
return j < blocks.length - 1 ? l + " ".repeat(GAP) : l;
|
|
2088
|
+
});
|
|
2089
|
+
output.push(parts.join(""));
|
|
2090
|
+
}
|
|
2091
|
+
output.push("");
|
|
2092
|
+
}
|
|
2093
|
+
return output.join("\n").trimEnd();
|
|
2132
2094
|
}
|
|
2133
|
-
function
|
|
2134
|
-
|
|
2135
|
-
|
|
2095
|
+
function fmtAi(text, error) {
|
|
2096
|
+
const lines = [cbold(ACCENT2, "Explanation:"), ""];
|
|
2097
|
+
if (error) {
|
|
2098
|
+
lines.push(fmtError(error));
|
|
2099
|
+
} else {
|
|
2100
|
+
lines.push(c(AI_COL, text));
|
|
2136
2101
|
}
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
);
|
|
2142
|
-
const
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2102
|
+
return lines.join("\n");
|
|
2103
|
+
}
|
|
2104
|
+
function fmtEntry(entry) {
|
|
2105
|
+
const isShell = entry.query.startsWith("!");
|
|
2106
|
+
const isCommand = !isShell && (entry.query.startsWith("/") || entry.query.startsWith("\\"));
|
|
2107
|
+
const label = isShell ? "Shell" : isCommand ? "Command" : "Query";
|
|
2108
|
+
const lines = [" " + fmtHeader(label, entry.query), ""];
|
|
2109
|
+
if (isShell) {
|
|
2110
|
+
if (entry.shellOutput !== null) lines.push(" " + entry.shellOutput.replace(/\n/g, "\n "));
|
|
2111
|
+
} else if (entry.commandMessage) {
|
|
2112
|
+
if (entry.commandMessage.helpData) {
|
|
2113
|
+
lines.push(fmtHelp(entry.commandMessage.helpData).replace(/\n/g, "\n "));
|
|
2114
|
+
} else if (entry.commandMessage.ok) {
|
|
2115
|
+
lines.push(" " + fmtOk(entry.commandMessage.text));
|
|
2116
|
+
} else {
|
|
2117
|
+
lines.push(" " + fmtError(entry.commandMessage.text));
|
|
2150
2118
|
}
|
|
2151
|
-
|
|
2119
|
+
} else if (entry.erdData) {
|
|
2120
|
+
lines.push(" " + fmtErd(entry.erdData).replace(/\n/g, "\n "));
|
|
2121
|
+
} else if (entry.aiResponse || entry.aiError) {
|
|
2122
|
+
lines.push(" " + fmtAi(entry.aiResponse, entry.aiError).replace(/\n/g, "\n "));
|
|
2123
|
+
} else {
|
|
2124
|
+
const result = fmtQueryResult(entry.queryState, entry.elapsed, entry.page, 50);
|
|
2125
|
+
if (result) lines.push(" " + result.replace(/\n/g, "\n "));
|
|
2126
|
+
}
|
|
2127
|
+
lines.push("");
|
|
2128
|
+
return lines.join("\n");
|
|
2152
2129
|
}
|
|
2153
2130
|
|
|
2154
2131
|
// src/ui/components/App.tsx
|
|
2155
|
-
import {
|
|
2156
|
-
var PAGE_SIZE = 50;
|
|
2132
|
+
import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
2157
2133
|
var PLACEHOLDER2 = "#a5b4fc";
|
|
2158
|
-
function activePageSize() {
|
|
2159
|
-
return Math.max(3, (process.stdout.rows ?? 24) - 21);
|
|
2160
|
-
}
|
|
2161
|
-
function limitLines(s, n) {
|
|
2162
|
-
const lines = s.split("\n");
|
|
2163
|
-
if (lines.length <= n) return s;
|
|
2164
|
-
return lines.slice(0, n).join("\n") + `
|
|
2165
|
-
\u2026 +${lines.length - n} more lines (scroll up after next submit)`;
|
|
2166
|
-
}
|
|
2167
2134
|
var QUERY_HEADER_COLOR = "#9ca3af";
|
|
2168
2135
|
function QueryHeader({ label, query }) {
|
|
2169
2136
|
const isMultiLine = query.includes("\n");
|
|
2170
2137
|
if (isMultiLine) {
|
|
2171
|
-
return /* @__PURE__ */
|
|
2172
|
-
/* @__PURE__ */
|
|
2173
|
-
/* @__PURE__ */
|
|
2174
|
-
/* @__PURE__ */
|
|
2138
|
+
return /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", children: [
|
|
2139
|
+
/* @__PURE__ */ jsxs5(Text5, { children: [
|
|
2140
|
+
/* @__PURE__ */ jsx5(Text5, { color: theme.accent, bold: true, children: "\u25CF " }),
|
|
2141
|
+
/* @__PURE__ */ jsxs5(Text5, { color: QUERY_HEADER_COLOR, bold: true, children: [
|
|
2175
2142
|
label,
|
|
2176
2143
|
":"
|
|
2177
2144
|
] })
|
|
2178
2145
|
] }),
|
|
2179
|
-
query.split("\n").map((line, i) => /* @__PURE__ */
|
|
2146
|
+
query.split("\n").map((line, i) => /* @__PURE__ */ jsxs5(Text5, { color: QUERY_HEADER_COLOR, bold: true, children: [
|
|
2180
2147
|
" ",
|
|
2181
2148
|
line
|
|
2182
2149
|
] }, i))
|
|
2183
2150
|
] });
|
|
2184
2151
|
}
|
|
2185
|
-
return /* @__PURE__ */
|
|
2186
|
-
/* @__PURE__ */
|
|
2187
|
-
/* @__PURE__ */
|
|
2152
|
+
return /* @__PURE__ */ jsxs5(Text5, { children: [
|
|
2153
|
+
/* @__PURE__ */ jsx5(Text5, { color: theme.accent, bold: true, children: "\u25CF " }),
|
|
2154
|
+
/* @__PURE__ */ jsxs5(Text5, { color: QUERY_HEADER_COLOR, bold: true, children: [
|
|
2188
2155
|
label,
|
|
2189
2156
|
"(",
|
|
2190
2157
|
query,
|
|
@@ -2192,55 +2159,31 @@ function QueryHeader({ label, query }) {
|
|
|
2192
2159
|
] })
|
|
2193
2160
|
] });
|
|
2194
2161
|
}
|
|
2195
|
-
var EntryView = memo(function EntryView2({ entry }) {
|
|
2196
|
-
const showAi = entry.aiResponse !== "" || entry.aiError !== null;
|
|
2197
|
-
const showErd = entry.erdData !== null;
|
|
2198
|
-
const isShell = entry.query.startsWith("!");
|
|
2199
|
-
const isCommand = !isShell && (entry.query.startsWith("/") || entry.query.startsWith("\\"));
|
|
2200
|
-
const label = isShell ? "Shell" : isCommand ? "Command" : "Query";
|
|
2201
|
-
return /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", marginBottom: 1, paddingX: 1, children: [
|
|
2202
|
-
/* @__PURE__ */ jsx7(QueryHeader, { label, query: entry.query }),
|
|
2203
|
-
/* @__PURE__ */ jsx7(Box7, { marginTop: 1, flexDirection: "column", children: isShell ? entry.shellOutput !== null && /* @__PURE__ */ jsx7(Text7, { children: entry.shellOutput || "(no output)" }) : /* @__PURE__ */ jsxs7(Fragment4, { children: [
|
|
2204
|
-
entry.commandMessage && (entry.commandMessage.helpData ? /* @__PURE__ */ jsx7(HelpView, { data: entry.commandMessage.helpData }) : entry.commandMessage.ok ? /* @__PURE__ */ jsxs7(Text7, { color: theme.accent, children: [
|
|
2205
|
-
"\u2713 ",
|
|
2206
|
-
entry.commandMessage.text
|
|
2207
|
-
] }) : /* @__PURE__ */ jsx7(ErrorBox, { message: entry.commandMessage.text })),
|
|
2208
|
-
showErd ? /* @__PURE__ */ jsx7(ErdView, { data: entry.erdData }) : showAi ? /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", children: [
|
|
2209
|
-
/* @__PURE__ */ jsx7(Text7, { color: theme.accent, bold: true, children: "Explanation:" }),
|
|
2210
|
-
/* @__PURE__ */ jsx7(Box7, { flexDirection: "column", marginTop: 1, children: entry.aiError ? /* @__PURE__ */ jsx7(ErrorBox, { message: entry.aiError }) : /* @__PURE__ */ jsx7(Text7, { color: PLACEHOLDER2, children: entry.aiResponse }) })
|
|
2211
|
-
] }) : !entry.commandMessage && /* @__PURE__ */ jsx7(QueryResult, { state: entry.queryState, elapsed: entry.elapsed, page: entry.page, pageSize: PAGE_SIZE })
|
|
2212
|
-
] }) })
|
|
2213
|
-
] });
|
|
2214
|
-
});
|
|
2215
2162
|
function App({ connectionState, aiUrl: aiUrl2, aiModel: aiModel2, aiKey: aiKey2, onChangeDatabase }) {
|
|
2216
2163
|
const { exit } = useApp();
|
|
2217
2164
|
const { isRawModeSupported } = useStdin2();
|
|
2218
|
-
const [
|
|
2219
|
-
const [
|
|
2220
|
-
const [
|
|
2221
|
-
const [lastResult, setLastResult] = useState3(null);
|
|
2222
|
-
const [page, setPage] = useState3(0);
|
|
2223
|
-
const [vimMode, setVimMode] = useState3("INSERT");
|
|
2224
|
-
const [inputIsShell, setInputIsShell] = useState3(false);
|
|
2225
|
-
const [elapsed, setElapsed] = useState3(null);
|
|
2226
|
-
const [vimEnabled, setVimEnabled] = useState3(true);
|
|
2227
|
-
const [commandMessage, setCommandMessage] = useState3(null);
|
|
2228
|
-
const [history, setHistory] = useState3(() => loadHistory());
|
|
2229
|
-
const [schema, setSchema] = useState3(null);
|
|
2230
|
-
const [aiResponse, setAiResponse] = useState3("");
|
|
2231
|
-
const [isStreaming, setIsStreaming] = useState3(false);
|
|
2232
|
-
const [aiError, setAiError] = useState3(null);
|
|
2233
|
-
const [shellOutput, setShellOutput] = useState3(null);
|
|
2234
|
-
const [isShellRunning, setIsShellRunning] = useState3(false);
|
|
2235
|
-
const [erdData, setErdData] = useState3(null);
|
|
2236
|
-
const [isErdLoading, setIsErdLoading] = useState3(false);
|
|
2237
|
-
const [completedEntries, setCompletedEntries] = useState3([]);
|
|
2238
|
-
const [clearSeq, setClearSeq] = useState3(0);
|
|
2165
|
+
const [active, setActive] = useState4({ type: "idle" });
|
|
2166
|
+
const [hasHistory, setHasHistory] = useState4(false);
|
|
2167
|
+
const [completedEntries, setCompletedEntries] = useState4([]);
|
|
2239
2168
|
const entryIdRef = useRef2(0);
|
|
2169
|
+
const [lastSqlQuery, setLastSqlQuery] = useState4("");
|
|
2170
|
+
const [lastResult, setLastResult] = useState4(null);
|
|
2171
|
+
const [lastResultPage, setLastResultPage] = useState4(0);
|
|
2172
|
+
const [vimMode, setVimMode] = useState4("INSERT");
|
|
2173
|
+
const [inputIsShell, setInputIsShell] = useState4(false);
|
|
2174
|
+
const [vimEnabled, setVimEnabled] = useState4(true);
|
|
2175
|
+
const [history, setHistory] = useState4(() => loadHistory());
|
|
2176
|
+
const [schema, setSchema] = useState4(null);
|
|
2240
2177
|
const aliasScope = connectionState.status === "connected" ? makeScope(connectionState.driver, connectionState.user, connectionState.host, connectionState.database) : "";
|
|
2241
|
-
const [aliases, setAliases] =
|
|
2178
|
+
const [aliases, setAliases] = useState4(
|
|
2242
2179
|
() => aliasScope ? getAllAliases(aliasScope) : {}
|
|
2243
2180
|
);
|
|
2181
|
+
function writeEntry(entry) {
|
|
2182
|
+
const formatted = fmtEntry(entry);
|
|
2183
|
+
const id = String(++entryIdRef.current);
|
|
2184
|
+
setCompletedEntries((prev) => [...prev, { id, formatted }]);
|
|
2185
|
+
setHasHistory(true);
|
|
2186
|
+
}
|
|
2244
2187
|
function handleSaveAlias(name, query) {
|
|
2245
2188
|
saveAlias(aliasScope, name, query);
|
|
2246
2189
|
setAliases(getAllAliases(aliasScope));
|
|
@@ -2266,10 +2209,6 @@ function App({ connectionState, aiUrl: aiUrl2, aiModel: aiModel2, aiKey: aiKey2,
|
|
|
2266
2209
|
process.off("SIGCONT", handleCont);
|
|
2267
2210
|
};
|
|
2268
2211
|
}, [isRawModeSupported]);
|
|
2269
|
-
useEffect4(() => {
|
|
2270
|
-
if (clearSeq === 0) return;
|
|
2271
|
-
process.stdout.write("\x1B[3J");
|
|
2272
|
-
}, [clearSeq]);
|
|
2273
2212
|
useEffect4(() => {
|
|
2274
2213
|
if (!isRawModeSupported) return;
|
|
2275
2214
|
process.stdout.write("\x1B[?25l");
|
|
@@ -2288,69 +2227,59 @@ function App({ connectionState, aiUrl: aiUrl2, aiModel: aiModel2, aiKey: aiKey2,
|
|
|
2288
2227
|
},
|
|
2289
2228
|
{ isActive: isRawModeSupported }
|
|
2290
2229
|
);
|
|
2291
|
-
async function
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
setIsStreaming(false);
|
|
2230
|
+
async function handleQuery(sql) {
|
|
2231
|
+
if (connectionState.status !== "connected") return;
|
|
2232
|
+
setLastSqlQuery(sql);
|
|
2233
|
+
setActive({ type: "loading", label: "Query", query: sql });
|
|
2234
|
+
const updated = addToHistory(history, sql);
|
|
2235
|
+
setHistory(updated);
|
|
2236
|
+
saveHistory(updated);
|
|
2237
|
+
const start = Date.now();
|
|
2238
|
+
const result = await runQuery(connectionState.client, sql);
|
|
2239
|
+
const elapsed = Date.now() - start;
|
|
2240
|
+
if (result.status === "success") {
|
|
2241
|
+
setLastResult(result.result);
|
|
2242
|
+
setLastResultPage(0);
|
|
2305
2243
|
}
|
|
2244
|
+
writeEntry({ query: sql, commandMessage: null, queryState: result, elapsed, page: 0, aiResponse: "", aiError: null, shellOutput: null, erdData: null });
|
|
2245
|
+
setActive({ type: "idle" });
|
|
2306
2246
|
}
|
|
2307
2247
|
async function handleErd() {
|
|
2308
2248
|
if (connectionState.status !== "connected") {
|
|
2309
|
-
|
|
2249
|
+
writeEntry({ query: "/erd", commandMessage: { ok: false, text: "Not connected." }, queryState: { status: "idle" }, elapsed: null, page: 0, aiResponse: "", aiError: null, shellOutput: null, erdData: null });
|
|
2310
2250
|
return;
|
|
2311
2251
|
}
|
|
2312
|
-
|
|
2313
|
-
setIsErdLoading(true);
|
|
2314
|
-
setCommandMessage(null);
|
|
2315
|
-
setQueryState({ status: "idle" });
|
|
2316
|
-
setAiResponse("");
|
|
2317
|
-
setAiError(null);
|
|
2252
|
+
setActive({ type: "loading", label: "Command", query: "/erd" });
|
|
2318
2253
|
try {
|
|
2319
2254
|
const data = await fetchErd(connectionState.client, connectionState.driver);
|
|
2320
|
-
|
|
2255
|
+
writeEntry({ query: "/erd", commandMessage: null, queryState: { status: "idle" }, elapsed: null, page: 0, aiResponse: "", aiError: null, shellOutput: null, erdData: data });
|
|
2321
2256
|
} catch (err) {
|
|
2322
|
-
|
|
2323
|
-
} finally {
|
|
2324
|
-
setIsErdLoading(false);
|
|
2257
|
+
writeEntry({ query: "/erd", commandMessage: { ok: false, text: err instanceof Error ? err.message : String(err) }, queryState: { status: "idle" }, elapsed: null, page: 0, aiResponse: "", aiError: null, shellOutput: null, erdData: null });
|
|
2325
2258
|
}
|
|
2259
|
+
setActive({ type: "idle" });
|
|
2326
2260
|
}
|
|
2327
|
-
async function
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
const start = Date.now();
|
|
2342
|
-
const result = await runQuery(connectionState.client, sql);
|
|
2343
|
-
setElapsed(Date.now() - start);
|
|
2344
|
-
setQueryState(result);
|
|
2345
|
-
if (result.status === "success") setLastResult(result.result);
|
|
2261
|
+
async function handleExplain(query) {
|
|
2262
|
+
setActive({ type: "streaming", query, text: "", error: null });
|
|
2263
|
+
let fullText = "";
|
|
2264
|
+
let error = null;
|
|
2265
|
+
try {
|
|
2266
|
+
for await (const chunk of streamExplain(query, aiUrl2, aiModel2, aiKey2 || void 0)) {
|
|
2267
|
+
fullText += chunk;
|
|
2268
|
+
setActive({ type: "streaming", query, text: fullText, error: null });
|
|
2269
|
+
}
|
|
2270
|
+
} catch (err) {
|
|
2271
|
+
error = err instanceof Error ? err.message : String(err);
|
|
2272
|
+
}
|
|
2273
|
+
writeEntry({ query: "/explain", commandMessage: null, queryState: { status: "idle" }, elapsed: null, page: 0, aiResponse: fullText, aiError: error, shellOutput: null, erdData: null });
|
|
2274
|
+
setActive({ type: "idle" });
|
|
2346
2275
|
}
|
|
2347
2276
|
function handleExport(format) {
|
|
2348
2277
|
if (!lastResult || lastResult.rows.length === 0) {
|
|
2349
|
-
|
|
2278
|
+
writeEntry({ query: `/export ${format}`, commandMessage: { ok: false, text: "No results to export \u2014 run a query first." }, queryState: { status: "idle" }, elapsed: null, page: 0, aiResponse: "", aiError: null, shellOutput: null, erdData: null });
|
|
2350
2279
|
return;
|
|
2351
2280
|
}
|
|
2352
2281
|
const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19);
|
|
2353
|
-
const filename =
|
|
2282
|
+
const filename = join5(homedir4(), `q-export-${ts}.${format}`);
|
|
2354
2283
|
try {
|
|
2355
2284
|
if (format === "json") {
|
|
2356
2285
|
writeFileSync5(filename, JSON.stringify(lastResult.rows, null, 2));
|
|
@@ -2366,57 +2295,36 @@ function App({ connectionState, aiUrl: aiUrl2, aiModel: aiModel2, aiKey: aiKey2,
|
|
|
2366
2295
|
);
|
|
2367
2296
|
writeFileSync5(filename, [header, ...rows].join("\n"));
|
|
2368
2297
|
}
|
|
2369
|
-
|
|
2298
|
+
writeEntry({ query: `/export ${format}`, commandMessage: { ok: true, text: `Exported to ${filename}` }, queryState: { status: "idle" }, elapsed: null, page: 0, aiResponse: "", aiError: null, shellOutput: null, erdData: null });
|
|
2370
2299
|
} catch (err) {
|
|
2371
|
-
|
|
2372
|
-
}
|
|
2373
|
-
}
|
|
2374
|
-
function snapshotActiveEntry() {
|
|
2375
|
-
if (lastQuery === "") return;
|
|
2376
|
-
setCompletedEntries((prev) => [
|
|
2377
|
-
...prev,
|
|
2378
|
-
{
|
|
2379
|
-
id: entryIdRef.current++,
|
|
2380
|
-
query: lastQuery,
|
|
2381
|
-
commandMessage,
|
|
2382
|
-
queryState,
|
|
2383
|
-
elapsed,
|
|
2384
|
-
page,
|
|
2385
|
-
aiResponse,
|
|
2386
|
-
aiError,
|
|
2387
|
-
shellOutput,
|
|
2388
|
-
erdData
|
|
2389
|
-
}
|
|
2390
|
-
]);
|
|
2300
|
+
writeEntry({ query: `/export ${format}`, commandMessage: { ok: false, text: err instanceof Error ? err.message : String(err) }, queryState: { status: "idle" }, elapsed: null, page: 0, aiResponse: "", aiError: null, shellOutput: null, erdData: null });
|
|
2301
|
+
}
|
|
2391
2302
|
}
|
|
2392
2303
|
async function handleShell(cmd) {
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
setIsShellRunning(true);
|
|
2396
|
-
setCommandMessage(null);
|
|
2397
|
-
setAiResponse("");
|
|
2398
|
-
setAiError(null);
|
|
2399
|
-
setQueryState({ status: "idle" });
|
|
2304
|
+
const q = `!${cmd}`;
|
|
2305
|
+
setActive({ type: "loading", label: "Shell", query: q });
|
|
2400
2306
|
const result = await runShell(cmd);
|
|
2401
2307
|
const combined = [result.stdout, result.stderr].filter(Boolean).join("\n");
|
|
2402
|
-
|
|
2403
|
-
|
|
2308
|
+
writeEntry({ query: q, commandMessage: null, queryState: { status: "idle" }, elapsed: null, page: 0, aiResponse: "", aiError: null, shellOutput: combined || "(no output)", erdData: null });
|
|
2309
|
+
setActive({ type: "idle" });
|
|
2404
2310
|
}
|
|
2405
2311
|
async function handleSubmit(sql) {
|
|
2406
2312
|
if (sql === "/next") {
|
|
2407
|
-
|
|
2313
|
+
if (!lastResult) return;
|
|
2314
|
+
const p = lastResultPage + 1;
|
|
2315
|
+
setLastResultPage(p);
|
|
2316
|
+
writeEntry({ query: "/next", commandMessage: null, queryState: { status: "success", result: lastResult }, elapsed: null, page: p, aiResponse: "", aiError: null, shellOutput: null, erdData: null });
|
|
2408
2317
|
return;
|
|
2409
2318
|
}
|
|
2410
2319
|
if (sql === "/prev") {
|
|
2411
|
-
|
|
2320
|
+
if (!lastResult || lastResultPage === 0) return;
|
|
2321
|
+
const p = lastResultPage - 1;
|
|
2322
|
+
setLastResultPage(p);
|
|
2323
|
+
writeEntry({ query: "/prev", commandMessage: null, queryState: { status: "success", result: lastResult }, elapsed: null, page: p, aiResponse: "", aiError: null, shellOutput: null, erdData: null });
|
|
2412
2324
|
return;
|
|
2413
2325
|
}
|
|
2414
|
-
snapshotActiveEntry();
|
|
2415
2326
|
if (sql.startsWith("!")) {
|
|
2416
|
-
|
|
2417
|
-
setLastQuery(sql);
|
|
2418
|
-
setShellOutput(null);
|
|
2419
|
-
void handleShell(cmd);
|
|
2327
|
+
void handleShell(sql.slice(1).trim());
|
|
2420
2328
|
return;
|
|
2421
2329
|
}
|
|
2422
2330
|
if (sql.startsWith("/") || sql.startsWith("\\")) {
|
|
@@ -2441,62 +2349,45 @@ function App({ connectionState, aiUrl: aiUrl2, aiModel: aiModel2, aiKey: aiKey2,
|
|
|
2441
2349
|
},
|
|
2442
2350
|
onClear: () => {
|
|
2443
2351
|
setCompletedEntries([]);
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
setShellOutput(null);
|
|
2448
|
-
setAiResponse("");
|
|
2449
|
-
setAiError(null);
|
|
2450
|
-
setElapsed(null);
|
|
2451
|
-
setPage(0);
|
|
2452
|
-
setClearSeq((s) => s + 1);
|
|
2352
|
+
setHasHistory(false);
|
|
2353
|
+
setActive({ type: "idle" });
|
|
2354
|
+
process.stdout.write("\x1B[H\x1B[2J\x1B[3J");
|
|
2453
2355
|
},
|
|
2454
2356
|
aliases,
|
|
2455
2357
|
onSaveAlias: handleSaveAlias,
|
|
2456
2358
|
onDeleteAlias: handleDeleteAlias
|
|
2457
2359
|
});
|
|
2458
2360
|
if (result.cleared) return;
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
setPage(0);
|
|
2465
|
-
setQueryState({ status: "idle" });
|
|
2466
|
-
if (result.helpData) setCommandMessage({ ok: result.ok, text: "", helpData: result.helpData });
|
|
2467
|
-
else if (result.message) setCommandMessage({ ok: result.ok, text: result.message });
|
|
2468
|
-
else setCommandMessage(null);
|
|
2361
|
+
if (result.helpData) {
|
|
2362
|
+
writeEntry({ query: sql, commandMessage: { ok: result.ok, text: "", helpData: result.helpData }, queryState: { status: "idle" }, elapsed: null, page: 0, aiResponse: "", aiError: null, shellOutput: null, erdData: null });
|
|
2363
|
+
} else if (result.message) {
|
|
2364
|
+
writeEntry({ query: sql, commandMessage: { ok: result.ok, text: result.message }, queryState: { status: "idle" }, elapsed: null, page: 0, aiResponse: "", aiError: null, shellOutput: null, erdData: null });
|
|
2365
|
+
}
|
|
2469
2366
|
return;
|
|
2470
2367
|
}
|
|
2471
2368
|
void handleQuery(sql);
|
|
2472
2369
|
}
|
|
2473
|
-
const isLoading =
|
|
2370
|
+
const isLoading = active.type !== "idle";
|
|
2474
2371
|
const isConnected = connectionState.status === "connected";
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
|
|
2478
|
-
|
|
2479
|
-
|
|
2480
|
-
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
|
|
2484
|
-
/* @__PURE__ */
|
|
2485
|
-
/* @__PURE__ */
|
|
2486
|
-
|
|
2487
|
-
|
|
2488
|
-
|
|
2489
|
-
|
|
2490
|
-
|
|
2491
|
-
|
|
2492
|
-
/* @__PURE__ */ jsx7(Box7, { flexDirection: "column", marginTop: 1, children: aiError ? /* @__PURE__ */ jsx7(ErrorBox, { message: aiError }) : /* @__PURE__ */ jsxs7(Text7, { color: PLACEHOLDER2, children: [
|
|
2493
|
-
aiResponse,
|
|
2494
|
-
isStreaming && /* @__PURE__ */ jsx7(Text7, { color: PLACEHOLDER2, children: "\u258B" })
|
|
2495
|
-
] }) })
|
|
2496
|
-
] }) : !commandMessage && /* @__PURE__ */ jsx7(QueryResult, { state: queryState, elapsed, page, pageSize: activePageSize() })
|
|
2497
|
-
] }) })
|
|
2372
|
+
return /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", children: [
|
|
2373
|
+
/* @__PURE__ */ jsx5(Static, { items: completedEntries, children: ({ id, formatted }) => /* @__PURE__ */ jsx5(Box5, { children: /* @__PURE__ */ jsx5(Text5, { children: formatted }) }, id) }),
|
|
2374
|
+
/* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", paddingX: 1, children: [
|
|
2375
|
+
!hasHistory && active.type === "idle" && /* @__PURE__ */ jsx5(Banner, { connectionState }),
|
|
2376
|
+
active.type === "loading" && /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", marginBottom: 1, children: [
|
|
2377
|
+
/* @__PURE__ */ jsx5(QueryHeader, { label: active.label, query: active.query }),
|
|
2378
|
+
/* @__PURE__ */ jsx5(Text5, { dimColor: true, children: " running\u2026" })
|
|
2379
|
+
] }),
|
|
2380
|
+
active.type === "streaming" && /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", marginBottom: 1, children: [
|
|
2381
|
+
/* @__PURE__ */ jsx5(QueryHeader, { label: "Query", query: active.query }),
|
|
2382
|
+
/* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", marginTop: 1, children: [
|
|
2383
|
+
/* @__PURE__ */ jsx5(Text5, { color: theme.accent, bold: true, children: "Explanation:" }),
|
|
2384
|
+
/* @__PURE__ */ jsx5(Box5, { marginTop: 1, children: active.error ? /* @__PURE__ */ jsx5(ErrorBox, { message: active.error }) : /* @__PURE__ */ jsxs5(Text5, { color: PLACEHOLDER2, children: [
|
|
2385
|
+
active.text,
|
|
2386
|
+
/* @__PURE__ */ jsx5(Text5, { color: PLACEHOLDER2, children: "\u258B" })
|
|
2387
|
+
] }) })
|
|
2388
|
+
] })
|
|
2498
2389
|
] }),
|
|
2499
|
-
isConnected ? /* @__PURE__ */
|
|
2390
|
+
isConnected ? /* @__PURE__ */ jsx5(
|
|
2500
2391
|
QueryInput,
|
|
2501
2392
|
{
|
|
2502
2393
|
onSubmit: handleSubmit,
|
|
@@ -2508,16 +2399,16 @@ function App({ connectionState, aiUrl: aiUrl2, aiModel: aiModel2, aiKey: aiKey2,
|
|
|
2508
2399
|
aliases,
|
|
2509
2400
|
schema: schema ?? void 0
|
|
2510
2401
|
}
|
|
2511
|
-
) : /* @__PURE__ */
|
|
2512
|
-
(vimEnabled || inputIsShell) && /* @__PURE__ */
|
|
2402
|
+
) : /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: "Not connected. Press Ctrl+C to exit." }),
|
|
2403
|
+
(vimEnabled || inputIsShell) && /* @__PURE__ */ jsx5(Box5, { marginTop: 1, children: /* @__PURE__ */ jsx5(Text5, { bold: true, color: inputIsShell ? theme.shellMode : vimMode === "NORMAL" ? theme.normalMode : theme.insertMode, children: isRawModeSupported ? inputIsShell ? "[SHELL]" : `[${vimMode}]` : "" }) })
|
|
2513
2404
|
] })
|
|
2514
2405
|
] });
|
|
2515
2406
|
}
|
|
2516
2407
|
|
|
2517
2408
|
// src/ui/components/ConnectionWizard.tsx
|
|
2518
|
-
import { useState as
|
|
2519
|
-
import { Box as
|
|
2520
|
-
import { jsx as
|
|
2409
|
+
import { useState as useState5 } from "react";
|
|
2410
|
+
import { Box as Box6, Text as Text6, useInput as useInput3, useStdin as useStdin3 } from "ink";
|
|
2411
|
+
import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
2521
2412
|
var DRIVERS = ["postgresql", "mysql", "sqlite"];
|
|
2522
2413
|
var LABEL_WIDTH = 10;
|
|
2523
2414
|
function fieldLabels(driver) {
|
|
@@ -2526,8 +2417,8 @@ function fieldLabels(driver) {
|
|
|
2526
2417
|
}
|
|
2527
2418
|
function ConnectionWizard({ onConnect, initialError }) {
|
|
2528
2419
|
const { isRawModeSupported } = useStdin3();
|
|
2529
|
-
const [focus, setFocus] =
|
|
2530
|
-
const [fields, setFields] =
|
|
2420
|
+
const [focus, setFocus] = useState5(0);
|
|
2421
|
+
const [fields, setFields] = useState5({
|
|
2531
2422
|
driver: "postgresql",
|
|
2532
2423
|
host: "localhost",
|
|
2533
2424
|
port: "5432",
|
|
@@ -2535,9 +2426,9 @@ function ConnectionWizard({ onConnect, initialError }) {
|
|
|
2535
2426
|
user: "",
|
|
2536
2427
|
password: ""
|
|
2537
2428
|
});
|
|
2538
|
-
const [connecting, setConnecting] =
|
|
2539
|
-
const [error, setError] =
|
|
2540
|
-
const [keychainHint, setKeychainHint] =
|
|
2429
|
+
const [connecting, setConnecting] = useState5(false);
|
|
2430
|
+
const [error, setError] = useState5(initialError ?? null);
|
|
2431
|
+
const [keychainHint, setKeychainHint] = useState5(false);
|
|
2541
2432
|
const labels = fieldLabels(fields.driver);
|
|
2542
2433
|
const fieldCount = labels.length;
|
|
2543
2434
|
function getTextValue(idx) {
|
|
@@ -2672,36 +2563,36 @@ function ConnectionWizard({ onConnect, initialError }) {
|
|
|
2672
2563
|
{ isActive: isRawModeSupported }
|
|
2673
2564
|
);
|
|
2674
2565
|
const isPassword = (idx) => fields.driver !== "sqlite" && idx === 5;
|
|
2675
|
-
return /* @__PURE__ */
|
|
2676
|
-
/* @__PURE__ */
|
|
2566
|
+
return /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", paddingX: 2, paddingTop: 2, paddingBottom: 1, children: [
|
|
2567
|
+
/* @__PURE__ */ jsx6(Box6, { marginBottom: 1, children: /* @__PURE__ */ jsx6(Text6, { bold: true, color: theme.accent, children: "Connect to a database" }) }),
|
|
2677
2568
|
labels.map((label, idx) => {
|
|
2678
2569
|
const isFocused = focus === idx;
|
|
2679
2570
|
const isDriver = idx === 0;
|
|
2680
|
-
return /* @__PURE__ */
|
|
2681
|
-
/* @__PURE__ */
|
|
2682
|
-
isDriver ? /* @__PURE__ */
|
|
2683
|
-
i > 0 && /* @__PURE__ */
|
|
2684
|
-
/* @__PURE__ */
|
|
2571
|
+
return /* @__PURE__ */ jsxs6(Box6, { children: [
|
|
2572
|
+
/* @__PURE__ */ jsx6(Text6, { color: isFocused ? theme.accent : void 0, bold: isFocused, children: label.padEnd(LABEL_WIDTH) }),
|
|
2573
|
+
isDriver ? /* @__PURE__ */ jsx6(Box6, { children: DRIVERS.map((d, i) => /* @__PURE__ */ jsxs6(Box6, { children: [
|
|
2574
|
+
i > 0 && /* @__PURE__ */ jsx6(Text6, { children: " " }),
|
|
2575
|
+
/* @__PURE__ */ jsxs6(Text6, { color: fields.driver === d ? theme.accent : void 0, bold: fields.driver === d, children: [
|
|
2685
2576
|
fields.driver === d ? "\u25CF " : "\u25CB ",
|
|
2686
2577
|
d.charAt(0).toUpperCase() + d.slice(1)
|
|
2687
2578
|
] })
|
|
2688
|
-
] }, d)) }) : /* @__PURE__ */
|
|
2689
|
-
/* @__PURE__ */
|
|
2690
|
-
isFocused && /* @__PURE__ */
|
|
2691
|
-
isFocused && isPassword(idx) && keychainHint && /* @__PURE__ */
|
|
2579
|
+
] }, d)) }) : /* @__PURE__ */ jsxs6(Box6, { children: [
|
|
2580
|
+
/* @__PURE__ */ jsx6(Text6, { children: isPassword(idx) ? "\u2022".repeat(getTextValue(idx).length) : getTextValue(idx) }),
|
|
2581
|
+
isFocused && /* @__PURE__ */ jsx6(Text6, { color: theme.accent, bold: true, children: "\u258C" }),
|
|
2582
|
+
isFocused && isPassword(idx) && keychainHint && /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: " (from keychain)" })
|
|
2692
2583
|
] })
|
|
2693
2584
|
] }, label);
|
|
2694
2585
|
}),
|
|
2695
|
-
/* @__PURE__ */
|
|
2586
|
+
/* @__PURE__ */ jsx6(Box6, { marginTop: 1, children: connecting ? /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: "Connecting\u2026" }) : error ? /* @__PURE__ */ jsxs6(Text6, { color: theme.error, children: [
|
|
2696
2587
|
"\u2717 ",
|
|
2697
2588
|
error
|
|
2698
2589
|
] }) : null }),
|
|
2699
|
-
/* @__PURE__ */
|
|
2590
|
+
/* @__PURE__ */ jsx6(Box6, { marginTop: 1, children: /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: "Tab \xB7 \u2191\u2193 navigate Enter connect \u2190\u2192 cycle driver" }) })
|
|
2700
2591
|
] });
|
|
2701
2592
|
}
|
|
2702
2593
|
|
|
2703
2594
|
// src/index.tsx
|
|
2704
|
-
import { jsx as
|
|
2595
|
+
import { jsx as jsx7 } from "react/jsx-runtime";
|
|
2705
2596
|
var { values } = parseArgs({
|
|
2706
2597
|
args: process.argv.slice(2).filter((a) => a !== "--"),
|
|
2707
2598
|
options: {
|
|
@@ -2717,8 +2608,8 @@ var aiUrl = values["ai-url"];
|
|
|
2717
2608
|
var aiModel = values["ai-model"];
|
|
2718
2609
|
var aiKey = values["api-key"] || process.env.Q_CLI_API_KEY || "";
|
|
2719
2610
|
function Root({ initialDsn: initialDsn2, aiUrl: aiUrl2, aiModel: aiModel2, aiKey: aiKey2 }) {
|
|
2720
|
-
const [connectionState, setConnectionState] =
|
|
2721
|
-
const [dsnError, setDsnError] =
|
|
2611
|
+
const [connectionState, setConnectionState] = useState6(null);
|
|
2612
|
+
const [dsnError, setDsnError] = useState6(null);
|
|
2722
2613
|
useEffect5(() => {
|
|
2723
2614
|
if (initialDsn2) {
|
|
2724
2615
|
connectDsn(initialDsn2).then((state) => {
|
|
@@ -2731,10 +2622,10 @@ function Root({ initialDsn: initialDsn2, aiUrl: aiUrl2, aiModel: aiModel2, aiKey
|
|
|
2731
2622
|
}
|
|
2732
2623
|
}, []);
|
|
2733
2624
|
if (initialDsn2 && !connectionState && !dsnError) {
|
|
2734
|
-
return /* @__PURE__ */
|
|
2625
|
+
return /* @__PURE__ */ jsx7(Box7, { paddingX: 2, paddingTop: 1, children: /* @__PURE__ */ jsx7(Text7, { dimColor: true, children: "Connecting\u2026" }) });
|
|
2735
2626
|
}
|
|
2736
2627
|
if (!connectionState) {
|
|
2737
|
-
return /* @__PURE__ */
|
|
2628
|
+
return /* @__PURE__ */ jsx7(ConnectionWizard, { onConnect: setConnectionState, initialError: dsnError ?? void 0 });
|
|
2738
2629
|
}
|
|
2739
2630
|
async function handleChangeDatabase(database) {
|
|
2740
2631
|
if (connectionState.status !== "connected") return;
|
|
@@ -2744,7 +2635,7 @@ function Root({ initialDsn: initialDsn2, aiUrl: aiUrl2, aiModel: aiModel2, aiKey
|
|
|
2744
2635
|
const next = await connectParams({ ...current.params, database });
|
|
2745
2636
|
setConnectionState(next);
|
|
2746
2637
|
}
|
|
2747
|
-
return /* @__PURE__ */
|
|
2638
|
+
return /* @__PURE__ */ jsx7(
|
|
2748
2639
|
App,
|
|
2749
2640
|
{
|
|
2750
2641
|
connectionState,
|
|
@@ -2758,6 +2649,6 @@ function Root({ initialDsn: initialDsn2, aiUrl: aiUrl2, aiModel: aiModel2, aiKey
|
|
|
2758
2649
|
);
|
|
2759
2650
|
}
|
|
2760
2651
|
render(
|
|
2761
|
-
/* @__PURE__ */
|
|
2652
|
+
/* @__PURE__ */ jsx7(Root, { initialDsn, aiUrl, aiModel, aiKey }),
|
|
2762
2653
|
{ exitOnCtrlC: true }
|
|
2763
2654
|
);
|