@deaquinodev/querky 0.4.3 → 0.4.4
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 +302 -55
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// src/index.tsx
|
|
4
4
|
import { useState as useState5, useEffect as useEffect5 } from "react";
|
|
5
5
|
import { parseArgs } from "util";
|
|
6
|
-
import { render, Box as
|
|
6
|
+
import { render, Box as Box9, Text as Text9 } from "ink";
|
|
7
7
|
|
|
8
8
|
// src/db/client.ts
|
|
9
9
|
import { Client } from "pg";
|
|
@@ -196,7 +196,7 @@ import { useState as useState3, useEffect as useEffect4, useRef as useRef2 } fro
|
|
|
196
196
|
import { writeFileSync as writeFileSync5 } from "fs";
|
|
197
197
|
import { homedir as homedir4 } from "os";
|
|
198
198
|
import { join as join6 } from "path";
|
|
199
|
-
import { Box as
|
|
199
|
+
import { Box as Box7, Text as Text7, Static, useApp, useInput as useInput2, useStdin as useStdin2 } from "ink";
|
|
200
200
|
|
|
201
201
|
// src/db/query.ts
|
|
202
202
|
async function runQuery(client, sql) {
|
|
@@ -678,6 +678,16 @@ var COMMANDS = {
|
|
|
678
678
|
return removed ? { ok: true, message: `Removed /${name}` } : { ok: false, message: `No alias named /${name}` };
|
|
679
679
|
}
|
|
680
680
|
},
|
|
681
|
+
"erd": {
|
|
682
|
+
description: "Visualize tables and relationships as a schema diagram",
|
|
683
|
+
category: "Schema",
|
|
684
|
+
usage: "/erd",
|
|
685
|
+
detail: "Fetches all tables, columns, and foreign key relationships from the connected database and renders a color-coded schema diagram. Each table gets a unique color; FK references use the color of the table they point to.",
|
|
686
|
+
run: (ctx) => {
|
|
687
|
+
ctx.onErd();
|
|
688
|
+
return { ok: true, message: "" };
|
|
689
|
+
}
|
|
690
|
+
},
|
|
681
691
|
"help": {
|
|
682
692
|
description: "Show help for all commands or a specific command",
|
|
683
693
|
category: "Session",
|
|
@@ -745,6 +755,83 @@ function runCommand(input, ctx) {
|
|
|
745
755
|
return { ok: false, message: `Unknown command: ${prefix}${rawName}` };
|
|
746
756
|
}
|
|
747
757
|
|
|
758
|
+
// src/db/erd.ts
|
|
759
|
+
function erdSql(driver) {
|
|
760
|
+
if (driver === "postgresql") {
|
|
761
|
+
return `
|
|
762
|
+
SELECT c.table_name, c.column_name, c.data_type,
|
|
763
|
+
(pk.column_name IS NOT NULL) AS is_pk,
|
|
764
|
+
fk.foreign_table AS fk_table
|
|
765
|
+
FROM information_schema.columns c
|
|
766
|
+
LEFT JOIN (
|
|
767
|
+
SELECT kcu.table_name, kcu.column_name
|
|
768
|
+
FROM information_schema.table_constraints tc
|
|
769
|
+
JOIN information_schema.key_column_usage kcu
|
|
770
|
+
ON tc.constraint_name = kcu.constraint_name AND tc.table_schema = kcu.table_schema
|
|
771
|
+
WHERE tc.constraint_type = 'PRIMARY KEY' AND tc.table_schema = 'public'
|
|
772
|
+
) pk ON pk.table_name = c.table_name AND pk.column_name = c.column_name
|
|
773
|
+
LEFT JOIN (
|
|
774
|
+
SELECT kcu.table_name, kcu.column_name, ccu.table_name AS foreign_table
|
|
775
|
+
FROM information_schema.table_constraints tc
|
|
776
|
+
JOIN information_schema.key_column_usage kcu
|
|
777
|
+
ON tc.constraint_name = kcu.constraint_name AND tc.table_schema = kcu.table_schema
|
|
778
|
+
JOIN information_schema.constraint_column_usage ccu
|
|
779
|
+
ON tc.constraint_name = ccu.constraint_name AND tc.table_schema = ccu.table_schema
|
|
780
|
+
WHERE tc.constraint_type = 'FOREIGN KEY' AND tc.table_schema = 'public'
|
|
781
|
+
) fk ON fk.table_name = c.table_name AND fk.column_name = c.column_name
|
|
782
|
+
WHERE c.table_schema = 'public'
|
|
783
|
+
ORDER BY c.table_name, c.ordinal_position
|
|
784
|
+
`;
|
|
785
|
+
}
|
|
786
|
+
if (driver === "mysql") {
|
|
787
|
+
return `
|
|
788
|
+
SELECT c.TABLE_NAME AS table_name, c.COLUMN_NAME AS column_name,
|
|
789
|
+
c.DATA_TYPE AS data_type,
|
|
790
|
+
(c.COLUMN_KEY = 'PRI') AS is_pk,
|
|
791
|
+
kcu.REFERENCED_TABLE_NAME AS fk_table
|
|
792
|
+
FROM information_schema.COLUMNS c
|
|
793
|
+
LEFT JOIN information_schema.KEY_COLUMN_USAGE kcu
|
|
794
|
+
ON kcu.TABLE_SCHEMA = c.TABLE_SCHEMA
|
|
795
|
+
AND kcu.TABLE_NAME = c.TABLE_NAME
|
|
796
|
+
AND kcu.COLUMN_NAME = c.COLUMN_NAME
|
|
797
|
+
AND kcu.REFERENCED_TABLE_NAME IS NOT NULL
|
|
798
|
+
WHERE c.TABLE_SCHEMA = DATABASE()
|
|
799
|
+
ORDER BY c.TABLE_NAME, c.ORDINAL_POSITION
|
|
800
|
+
`;
|
|
801
|
+
}
|
|
802
|
+
return `
|
|
803
|
+
SELECT m.name AS table_name, p.name AS column_name,
|
|
804
|
+
p.type AS data_type,
|
|
805
|
+
(p.pk > 0) AS is_pk,
|
|
806
|
+
f."table" AS fk_table
|
|
807
|
+
FROM sqlite_master m
|
|
808
|
+
JOIN pragma_table_info(m.name) p
|
|
809
|
+
LEFT JOIN pragma_foreign_key_list(m.name) f ON f."from" = p.name
|
|
810
|
+
WHERE m.type = 'table' AND m.name NOT LIKE 'sqlite_%'
|
|
811
|
+
ORDER BY m.name, p.cid
|
|
812
|
+
`;
|
|
813
|
+
}
|
|
814
|
+
async function fetchErd(client, driver) {
|
|
815
|
+
const result = await client.query(erdSql(driver));
|
|
816
|
+
const tableMap = /* @__PURE__ */ new Map();
|
|
817
|
+
for (const row of result.rows) {
|
|
818
|
+
const tableName = String(row["table_name"] ?? "");
|
|
819
|
+
if (!tableMap.has(tableName)) tableMap.set(tableName, []);
|
|
820
|
+
const cols = tableMap.get(tableName);
|
|
821
|
+
const colName = String(row["column_name"] ?? "");
|
|
822
|
+
if (cols.some((c) => c.name === colName)) continue;
|
|
823
|
+
cols.push({
|
|
824
|
+
name: colName,
|
|
825
|
+
type: String(row["data_type"] ?? ""),
|
|
826
|
+
isPk: Boolean(row["is_pk"]),
|
|
827
|
+
fkTable: row["fk_table"] ? String(row["fk_table"]) : void 0
|
|
828
|
+
});
|
|
829
|
+
}
|
|
830
|
+
return {
|
|
831
|
+
tables: Array.from(tableMap.entries()).map(([name, columns]) => ({ name, columns }))
|
|
832
|
+
};
|
|
833
|
+
}
|
|
834
|
+
|
|
748
835
|
// src/commands/shell.ts
|
|
749
836
|
import { exec } from "child_process";
|
|
750
837
|
import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, unlinkSync } from "fs";
|
|
@@ -1894,8 +1981,138 @@ function HelpView({ data }) {
|
|
|
1894
1981
|
return null;
|
|
1895
1982
|
}
|
|
1896
1983
|
|
|
1897
|
-
// src/ui/components/
|
|
1984
|
+
// src/ui/components/ErdView.tsx
|
|
1985
|
+
import { Box as Box6, Text as Text6 } from "ink";
|
|
1898
1986
|
import { Fragment as Fragment3, jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
1987
|
+
var TABLE_COLORS = [
|
|
1988
|
+
"#f472b6",
|
|
1989
|
+
"#34d399",
|
|
1990
|
+
"#fb923c",
|
|
1991
|
+
"#60a5fa",
|
|
1992
|
+
"#a78bfa",
|
|
1993
|
+
"#f87171",
|
|
1994
|
+
"#fbbf24",
|
|
1995
|
+
"#2dd4bf"
|
|
1996
|
+
];
|
|
1997
|
+
var BORDER2 = "white";
|
|
1998
|
+
var PAD = 1;
|
|
1999
|
+
var GAP = 2;
|
|
2000
|
+
var FK_PREFIX = "FK \u2192 ";
|
|
2001
|
+
function computeMetrics(table) {
|
|
2002
|
+
const nameW = Math.max(2, ...table.columns.map((c) => c.name.length));
|
|
2003
|
+
const typeW = Math.max(4, ...table.columns.map((c) => c.type.length));
|
|
2004
|
+
const keyW = Math.max(
|
|
2005
|
+
2,
|
|
2006
|
+
...table.columns.map((c) => {
|
|
2007
|
+
if (c.isPk) return 2;
|
|
2008
|
+
if (c.fkTable) return FK_PREFIX.length + c.fkTable.length;
|
|
2009
|
+
return 0;
|
|
2010
|
+
})
|
|
2011
|
+
);
|
|
2012
|
+
const totalW = 4 + 3 * PAD * 2 + nameW + typeW + keyW;
|
|
2013
|
+
return { nameW, typeW, keyW, totalW };
|
|
2014
|
+
}
|
|
2015
|
+
function groupIntoRows(metrics, termW) {
|
|
2016
|
+
const rows = [];
|
|
2017
|
+
let row = [];
|
|
2018
|
+
let usedW = 0;
|
|
2019
|
+
for (let i = 0; i < metrics.length; i++) {
|
|
2020
|
+
const w = metrics[i].totalW;
|
|
2021
|
+
if (row.length === 0) {
|
|
2022
|
+
row.push(i);
|
|
2023
|
+
usedW = w;
|
|
2024
|
+
} else if (usedW + GAP + w <= termW) {
|
|
2025
|
+
row.push(i);
|
|
2026
|
+
usedW += GAP + w;
|
|
2027
|
+
} else {
|
|
2028
|
+
rows.push(row);
|
|
2029
|
+
row = [i];
|
|
2030
|
+
usedW = w;
|
|
2031
|
+
}
|
|
2032
|
+
}
|
|
2033
|
+
if (row.length > 0) rows.push(row);
|
|
2034
|
+
return rows;
|
|
2035
|
+
}
|
|
2036
|
+
function TableBox({ table, m, color, colorMap }) {
|
|
2037
|
+
const { nameW, typeW, keyW, totalW } = m;
|
|
2038
|
+
const sp = " ".repeat(PAD);
|
|
2039
|
+
const p = (s, w) => s.slice(0, w).padEnd(w);
|
|
2040
|
+
const top = "\u256D" + "\u2500".repeat(totalW - 2) + "\u256E";
|
|
2041
|
+
const sep = "\u251C" + "\u2500".repeat(nameW + PAD * 2) + "\u252C" + "\u2500".repeat(typeW + PAD * 2) + "\u252C" + "\u2500".repeat(keyW + PAD * 2) + "\u2524";
|
|
2042
|
+
const bot = "\u2570" + "\u2500".repeat(nameW + PAD * 2) + "\u2534" + "\u2500".repeat(typeW + PAD * 2) + "\u2534" + "\u2500".repeat(keyW + PAD * 2) + "\u256F";
|
|
2043
|
+
const headerW = totalW - 4;
|
|
2044
|
+
return /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", children: [
|
|
2045
|
+
/* @__PURE__ */ jsx6(Text6, { color: BORDER2, children: top }),
|
|
2046
|
+
/* @__PURE__ */ jsxs6(Box6, { children: [
|
|
2047
|
+
/* @__PURE__ */ jsx6(Text6, { color: BORDER2, children: "\u2502" }),
|
|
2048
|
+
/* @__PURE__ */ jsxs6(Text6, { color, bold: true, children: [
|
|
2049
|
+
sp,
|
|
2050
|
+
p(table.name, headerW),
|
|
2051
|
+
sp
|
|
2052
|
+
] }),
|
|
2053
|
+
/* @__PURE__ */ jsx6(Text6, { color: BORDER2, children: "\u2502" })
|
|
2054
|
+
] }),
|
|
2055
|
+
/* @__PURE__ */ jsx6(Text6, { color: BORDER2, children: sep }),
|
|
2056
|
+
table.columns.map((col, i) => /* @__PURE__ */ jsxs6(Box6, { children: [
|
|
2057
|
+
/* @__PURE__ */ jsx6(Text6, { color: BORDER2, children: "\u2502" }),
|
|
2058
|
+
/* @__PURE__ */ jsxs6(Text6, { children: [
|
|
2059
|
+
sp,
|
|
2060
|
+
p(col.name, nameW),
|
|
2061
|
+
sp
|
|
2062
|
+
] }),
|
|
2063
|
+
/* @__PURE__ */ jsx6(Text6, { color: BORDER2, children: "\u2502" }),
|
|
2064
|
+
/* @__PURE__ */ jsxs6(Text6, { dimColor: true, children: [
|
|
2065
|
+
sp,
|
|
2066
|
+
p(col.type, typeW),
|
|
2067
|
+
sp
|
|
2068
|
+
] }),
|
|
2069
|
+
/* @__PURE__ */ jsx6(Text6, { color: BORDER2, children: "\u2502" }),
|
|
2070
|
+
col.isPk ? /* @__PURE__ */ jsxs6(Text6, { bold: true, children: [
|
|
2071
|
+
sp,
|
|
2072
|
+
p("PK", keyW),
|
|
2073
|
+
sp
|
|
2074
|
+
] }) : col.fkTable ? /* @__PURE__ */ jsxs6(Fragment3, { children: [
|
|
2075
|
+
/* @__PURE__ */ jsxs6(Text6, { dimColor: true, children: [
|
|
2076
|
+
sp,
|
|
2077
|
+
FK_PREFIX
|
|
2078
|
+
] }),
|
|
2079
|
+
/* @__PURE__ */ jsxs6(Text6, { color: colorMap.get(col.fkTable) ?? BORDER2, children: [
|
|
2080
|
+
p(col.fkTable, keyW - FK_PREFIX.length),
|
|
2081
|
+
sp
|
|
2082
|
+
] })
|
|
2083
|
+
] }) : /* @__PURE__ */ jsxs6(Text6, { children: [
|
|
2084
|
+
sp,
|
|
2085
|
+
" ".repeat(keyW),
|
|
2086
|
+
sp
|
|
2087
|
+
] }),
|
|
2088
|
+
/* @__PURE__ */ jsx6(Text6, { color: BORDER2, children: "\u2502" })
|
|
2089
|
+
] }, i)),
|
|
2090
|
+
/* @__PURE__ */ jsx6(Text6, { color: BORDER2, children: bot })
|
|
2091
|
+
] });
|
|
2092
|
+
}
|
|
2093
|
+
function ErdView({ data }) {
|
|
2094
|
+
if (data.tables.length === 0) {
|
|
2095
|
+
return /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: "No tables found in the current schema." });
|
|
2096
|
+
}
|
|
2097
|
+
const termW = process.stdout.columns ?? 80;
|
|
2098
|
+
const metrics = data.tables.map(computeMetrics);
|
|
2099
|
+
const colorMap = new Map(
|
|
2100
|
+
data.tables.map((t, i) => [t.name, TABLE_COLORS[i % TABLE_COLORS.length]])
|
|
2101
|
+
);
|
|
2102
|
+
const rows = groupIntoRows(metrics, termW);
|
|
2103
|
+
return /* @__PURE__ */ jsx6(Box6, { flexDirection: "column", marginTop: 1, children: rows.map((row, ri) => /* @__PURE__ */ jsx6(Box6, { flexDirection: "row", marginBottom: ri < rows.length - 1 ? 1 : 0, children: row.map((ti, j) => /* @__PURE__ */ jsx6(Box6, { marginRight: j < row.length - 1 ? GAP : 0, children: /* @__PURE__ */ jsx6(
|
|
2104
|
+
TableBox,
|
|
2105
|
+
{
|
|
2106
|
+
table: data.tables[ti],
|
|
2107
|
+
m: metrics[ti],
|
|
2108
|
+
color: colorMap.get(data.tables[ti].name) ?? BORDER2,
|
|
2109
|
+
colorMap
|
|
2110
|
+
}
|
|
2111
|
+
) }, ti)) }, ri)) });
|
|
2112
|
+
}
|
|
2113
|
+
|
|
2114
|
+
// src/ui/components/App.tsx
|
|
2115
|
+
import { Fragment as Fragment4, jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
1899
2116
|
var PAGE_SIZE = 50;
|
|
1900
2117
|
var PLACEHOLDER2 = "#a5b4fc";
|
|
1901
2118
|
function activePageSize() {
|
|
@@ -1909,26 +2126,27 @@ function limitLines(s, n) {
|
|
|
1909
2126
|
}
|
|
1910
2127
|
function EntryView({ entry }) {
|
|
1911
2128
|
const showAi = entry.aiResponse !== "" || entry.aiError !== null;
|
|
2129
|
+
const showErd = entry.erdData !== null;
|
|
1912
2130
|
const isShell = entry.query.startsWith("!");
|
|
1913
2131
|
const isCommand = !isShell && (entry.query.startsWith("/") || entry.query.startsWith("\\"));
|
|
1914
2132
|
const label = isShell ? "Shell:" : isCommand ? "Command:" : "Query:";
|
|
1915
|
-
return /* @__PURE__ */
|
|
1916
|
-
/* @__PURE__ */
|
|
1917
|
-
/* @__PURE__ */
|
|
2133
|
+
return /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", marginBottom: 1, paddingX: 1, children: [
|
|
2134
|
+
/* @__PURE__ */ jsxs7(Box7, { borderStyle: "round", borderColor: theme.accent, paddingX: 1, children: [
|
|
2135
|
+
/* @__PURE__ */ jsxs7(Text7, { color: theme.accent, bold: true, children: [
|
|
1918
2136
|
label,
|
|
1919
2137
|
" "
|
|
1920
2138
|
] }),
|
|
1921
|
-
/* @__PURE__ */
|
|
2139
|
+
/* @__PURE__ */ jsx7(Text7, { dimColor: true, children: entry.query })
|
|
1922
2140
|
] }),
|
|
1923
|
-
/* @__PURE__ */
|
|
1924
|
-
entry.commandMessage && (entry.commandMessage.helpData ? /* @__PURE__ */
|
|
2141
|
+
/* @__PURE__ */ jsx7(Box7, { marginTop: 1, flexDirection: "column", children: isShell ? entry.shellOutput !== null && /* @__PURE__ */ jsx7(Text7, { children: entry.shellOutput || "(no output)" }) : /* @__PURE__ */ jsxs7(Fragment4, { children: [
|
|
2142
|
+
entry.commandMessage && (entry.commandMessage.helpData ? /* @__PURE__ */ jsx7(HelpView, { data: entry.commandMessage.helpData }) : entry.commandMessage.ok ? /* @__PURE__ */ jsxs7(Text7, { color: theme.accent, children: [
|
|
1925
2143
|
"\u2713 ",
|
|
1926
2144
|
entry.commandMessage.text
|
|
1927
|
-
] }) : /* @__PURE__ */
|
|
1928
|
-
showAi ? /* @__PURE__ */
|
|
1929
|
-
/* @__PURE__ */
|
|
1930
|
-
/* @__PURE__ */
|
|
1931
|
-
] }) : !entry.commandMessage && /* @__PURE__ */
|
|
2145
|
+
] }) : /* @__PURE__ */ jsx7(ErrorBox, { message: entry.commandMessage.text })),
|
|
2146
|
+
showErd ? /* @__PURE__ */ jsx7(ErdView, { data: entry.erdData }) : showAi ? /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", children: [
|
|
2147
|
+
/* @__PURE__ */ jsx7(Text7, { color: theme.accent, bold: true, children: "Explanation:" }),
|
|
2148
|
+
/* @__PURE__ */ jsx7(Box7, { flexDirection: "column", marginTop: 1, children: entry.aiError ? /* @__PURE__ */ jsx7(ErrorBox, { message: entry.aiError }) : /* @__PURE__ */ jsx7(Text7, { color: PLACEHOLDER2, children: entry.aiResponse }) })
|
|
2149
|
+
] }) : !entry.commandMessage && /* @__PURE__ */ jsx7(QueryResult, { state: entry.queryState, elapsed: entry.elapsed, page: entry.page, pageSize: PAGE_SIZE })
|
|
1932
2150
|
] }) })
|
|
1933
2151
|
] });
|
|
1934
2152
|
}
|
|
@@ -1952,6 +2170,8 @@ function App({ connectionState, aiUrl: aiUrl2, aiModel: aiModel2, aiKey: aiKey2,
|
|
|
1952
2170
|
const [aiError, setAiError] = useState3(null);
|
|
1953
2171
|
const [shellOutput, setShellOutput] = useState3(null);
|
|
1954
2172
|
const [isShellRunning, setIsShellRunning] = useState3(false);
|
|
2173
|
+
const [erdData, setErdData] = useState3(null);
|
|
2174
|
+
const [isErdLoading, setIsErdLoading] = useState3(false);
|
|
1955
2175
|
const [completedEntries, setCompletedEntries] = useState3([]);
|
|
1956
2176
|
const entryIdRef = useRef2(0);
|
|
1957
2177
|
const aliasScope = connectionState.status === "connected" ? makeScope(connectionState.driver, connectionState.user, connectionState.host, connectionState.database) : "";
|
|
@@ -2017,8 +2237,29 @@ function App({ connectionState, aiUrl: aiUrl2, aiModel: aiModel2, aiKey: aiKey2,
|
|
|
2017
2237
|
setIsStreaming(false);
|
|
2018
2238
|
}
|
|
2019
2239
|
}
|
|
2240
|
+
async function handleErd() {
|
|
2241
|
+
if (connectionState.status !== "connected") {
|
|
2242
|
+
setCommandMessage({ ok: false, text: "Not connected to a database." });
|
|
2243
|
+
return;
|
|
2244
|
+
}
|
|
2245
|
+
setErdData(null);
|
|
2246
|
+
setIsErdLoading(true);
|
|
2247
|
+
setCommandMessage(null);
|
|
2248
|
+
setQueryState({ status: "idle" });
|
|
2249
|
+
setAiResponse("");
|
|
2250
|
+
setAiError(null);
|
|
2251
|
+
try {
|
|
2252
|
+
const data = await fetchErd(connectionState.client, connectionState.driver);
|
|
2253
|
+
setErdData(data);
|
|
2254
|
+
} catch (err) {
|
|
2255
|
+
setCommandMessage({ ok: false, text: err instanceof Error ? err.message : String(err) });
|
|
2256
|
+
} finally {
|
|
2257
|
+
setIsErdLoading(false);
|
|
2258
|
+
}
|
|
2259
|
+
}
|
|
2020
2260
|
async function handleQuery(sql) {
|
|
2021
2261
|
if (connectionState.status !== "connected") return;
|
|
2262
|
+
setErdData(null);
|
|
2022
2263
|
setAiResponse("");
|
|
2023
2264
|
setAiError(null);
|
|
2024
2265
|
setCommandMessage(null);
|
|
@@ -2076,11 +2317,13 @@ function App({ connectionState, aiUrl: aiUrl2, aiModel: aiModel2, aiKey: aiKey2,
|
|
|
2076
2317
|
page,
|
|
2077
2318
|
aiResponse,
|
|
2078
2319
|
aiError,
|
|
2079
|
-
shellOutput
|
|
2320
|
+
shellOutput,
|
|
2321
|
+
erdData
|
|
2080
2322
|
}
|
|
2081
2323
|
]);
|
|
2082
2324
|
}
|
|
2083
2325
|
async function handleShell(cmd) {
|
|
2326
|
+
setErdData(null);
|
|
2084
2327
|
setShellOutput(null);
|
|
2085
2328
|
setIsShellRunning(true);
|
|
2086
2329
|
setCommandMessage(null);
|
|
@@ -2126,6 +2369,9 @@ function App({ connectionState, aiUrl: aiUrl2, aiModel: aiModel2, aiKey: aiKey2,
|
|
|
2126
2369
|
onChangeDatabase?.(db);
|
|
2127
2370
|
},
|
|
2128
2371
|
onExport: handleExport,
|
|
2372
|
+
onErd: () => {
|
|
2373
|
+
void handleErd();
|
|
2374
|
+
},
|
|
2129
2375
|
onClear: () => {
|
|
2130
2376
|
process.stdout.write("\x1B[2J\x1B[3J\x1B[H");
|
|
2131
2377
|
setCompletedEntries([]);
|
|
@@ -2144,6 +2390,7 @@ function App({ connectionState, aiUrl: aiUrl2, aiModel: aiModel2, aiKey: aiKey2,
|
|
|
2144
2390
|
});
|
|
2145
2391
|
if (result.cleared) return;
|
|
2146
2392
|
setLastQuery(sql);
|
|
2393
|
+
setErdData(null);
|
|
2147
2394
|
setAiResponse("");
|
|
2148
2395
|
setAiError(null);
|
|
2149
2396
|
setElapsed(null);
|
|
@@ -2156,39 +2403,39 @@ function App({ connectionState, aiUrl: aiUrl2, aiModel: aiModel2, aiKey: aiKey2,
|
|
|
2156
2403
|
}
|
|
2157
2404
|
void handleQuery(sql);
|
|
2158
2405
|
}
|
|
2159
|
-
const isLoading = queryState.status === "running" || isStreaming || isShellRunning;
|
|
2406
|
+
const isLoading = queryState.status === "running" || isStreaming || isShellRunning || isErdLoading;
|
|
2160
2407
|
const isConnected = connectionState.status === "connected";
|
|
2161
2408
|
const showAi = aiResponse !== "" || isStreaming || aiError !== null;
|
|
2162
2409
|
const isShellEntry = lastQuery.startsWith("!");
|
|
2163
2410
|
const isCommand = !isShellEntry && (lastQuery.startsWith("/") || lastQuery.startsWith("\\"));
|
|
2164
2411
|
const activeLabel = isShellEntry ? "Shell:" : isCommand ? "Command:" : "Query:";
|
|
2165
|
-
return /* @__PURE__ */
|
|
2166
|
-
/* @__PURE__ */
|
|
2167
|
-
/* @__PURE__ */
|
|
2168
|
-
lastQuery === "" && /* @__PURE__ */
|
|
2169
|
-
lastQuery !== "" && /* @__PURE__ */
|
|
2170
|
-
/* @__PURE__ */
|
|
2171
|
-
/* @__PURE__ */
|
|
2412
|
+
return /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", children: [
|
|
2413
|
+
/* @__PURE__ */ jsx7(Static, { items: completedEntries, children: (entry) => /* @__PURE__ */ jsx7(EntryView, { entry }, entry.id) }),
|
|
2414
|
+
/* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", paddingX: 1, children: [
|
|
2415
|
+
lastQuery === "" && /* @__PURE__ */ jsx7(Banner, { connectionState }),
|
|
2416
|
+
lastQuery !== "" && /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", marginBottom: 2, children: [
|
|
2417
|
+
/* @__PURE__ */ jsxs7(Box7, { borderStyle: "round", borderColor: theme.accent, paddingX: 1, children: [
|
|
2418
|
+
/* @__PURE__ */ jsxs7(Text7, { color: theme.accent, bold: true, children: [
|
|
2172
2419
|
activeLabel,
|
|
2173
2420
|
" "
|
|
2174
2421
|
] }),
|
|
2175
|
-
/* @__PURE__ */
|
|
2422
|
+
/* @__PURE__ */ jsx7(Text7, { dimColor: true, children: lastQuery })
|
|
2176
2423
|
] }),
|
|
2177
|
-
/* @__PURE__ */
|
|
2178
|
-
commandMessage && (commandMessage.helpData ? /* @__PURE__ */
|
|
2424
|
+
/* @__PURE__ */ jsx7(Box7, { marginTop: 1, flexDirection: "column", children: isShellEntry ? isShellRunning ? /* @__PURE__ */ jsx7(Text7, { dimColor: true, children: "running\u2026" }) : /* @__PURE__ */ jsx7(Text7, { children: limitLines(shellOutput ?? "", activePageSize()) }) : /* @__PURE__ */ jsxs7(Fragment4, { children: [
|
|
2425
|
+
commandMessage && (commandMessage.helpData ? /* @__PURE__ */ jsx7(HelpView, { data: commandMessage.helpData }) : commandMessage.ok ? /* @__PURE__ */ jsxs7(Text7, { color: theme.accent, children: [
|
|
2179
2426
|
"\u2713 ",
|
|
2180
2427
|
commandMessage.text
|
|
2181
|
-
] }) : /* @__PURE__ */
|
|
2182
|
-
showAi ? /* @__PURE__ */
|
|
2183
|
-
/* @__PURE__ */
|
|
2184
|
-
/* @__PURE__ */
|
|
2428
|
+
] }) : /* @__PURE__ */ jsx7(ErrorBox, { message: commandMessage.text })),
|
|
2429
|
+
isErdLoading ? /* @__PURE__ */ jsx7(Text7, { dimColor: true, children: "Fetching schema\u2026" }) : erdData ? /* @__PURE__ */ jsx7(ErdView, { data: erdData }) : showAi ? /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", children: [
|
|
2430
|
+
/* @__PURE__ */ jsx7(Text7, { color: theme.accent, bold: true, children: "Explanation:" }),
|
|
2431
|
+
/* @__PURE__ */ jsx7(Box7, { flexDirection: "column", marginTop: 1, children: aiError ? /* @__PURE__ */ jsx7(ErrorBox, { message: aiError }) : /* @__PURE__ */ jsxs7(Text7, { color: PLACEHOLDER2, children: [
|
|
2185
2432
|
aiResponse,
|
|
2186
|
-
isStreaming && /* @__PURE__ */
|
|
2433
|
+
isStreaming && /* @__PURE__ */ jsx7(Text7, { color: PLACEHOLDER2, children: "\u258B" })
|
|
2187
2434
|
] }) })
|
|
2188
|
-
] }) : !commandMessage && /* @__PURE__ */
|
|
2435
|
+
] }) : !commandMessage && /* @__PURE__ */ jsx7(QueryResult, { state: queryState, elapsed, page, pageSize: activePageSize() })
|
|
2189
2436
|
] }) })
|
|
2190
2437
|
] }),
|
|
2191
|
-
isConnected ? /* @__PURE__ */
|
|
2438
|
+
isConnected ? /* @__PURE__ */ jsx7(
|
|
2192
2439
|
QueryInput,
|
|
2193
2440
|
{
|
|
2194
2441
|
onSubmit: handleSubmit,
|
|
@@ -2200,16 +2447,16 @@ function App({ connectionState, aiUrl: aiUrl2, aiModel: aiModel2, aiKey: aiKey2,
|
|
|
2200
2447
|
aliases,
|
|
2201
2448
|
schema: schema ?? void 0
|
|
2202
2449
|
}
|
|
2203
|
-
) : /* @__PURE__ */
|
|
2204
|
-
(vimEnabled || inputIsShell) && /* @__PURE__ */
|
|
2450
|
+
) : /* @__PURE__ */ jsx7(Text7, { dimColor: true, children: "Not connected. Press Ctrl+C to exit." }),
|
|
2451
|
+
(vimEnabled || inputIsShell) && /* @__PURE__ */ jsx7(Box7, { marginTop: 1, children: /* @__PURE__ */ jsx7(Text7, { bold: true, color: inputIsShell ? theme.shellMode : vimMode === "NORMAL" ? theme.normalMode : theme.insertMode, children: isRawModeSupported ? inputIsShell ? "[SHELL]" : `[${vimMode}]` : "" }) })
|
|
2205
2452
|
] })
|
|
2206
2453
|
] });
|
|
2207
2454
|
}
|
|
2208
2455
|
|
|
2209
2456
|
// src/ui/components/ConnectionWizard.tsx
|
|
2210
2457
|
import { useState as useState4 } from "react";
|
|
2211
|
-
import { Box as
|
|
2212
|
-
import { jsx as
|
|
2458
|
+
import { Box as Box8, Text as Text8, useInput as useInput3, useStdin as useStdin3 } from "ink";
|
|
2459
|
+
import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
2213
2460
|
var DRIVERS = ["postgresql", "mysql", "sqlite"];
|
|
2214
2461
|
var LABEL_WIDTH = 10;
|
|
2215
2462
|
function fieldLabels(driver) {
|
|
@@ -2364,36 +2611,36 @@ function ConnectionWizard({ onConnect, initialError }) {
|
|
|
2364
2611
|
{ isActive: isRawModeSupported }
|
|
2365
2612
|
);
|
|
2366
2613
|
const isPassword = (idx) => fields.driver !== "sqlite" && idx === 5;
|
|
2367
|
-
return /* @__PURE__ */
|
|
2368
|
-
/* @__PURE__ */
|
|
2614
|
+
return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", paddingX: 2, paddingTop: 2, paddingBottom: 1, children: [
|
|
2615
|
+
/* @__PURE__ */ jsx8(Box8, { marginBottom: 1, children: /* @__PURE__ */ jsx8(Text8, { bold: true, color: theme.accent, children: "Connect to a database" }) }),
|
|
2369
2616
|
labels.map((label, idx) => {
|
|
2370
2617
|
const isFocused = focus === idx;
|
|
2371
2618
|
const isDriver = idx === 0;
|
|
2372
|
-
return /* @__PURE__ */
|
|
2373
|
-
/* @__PURE__ */
|
|
2374
|
-
isDriver ? /* @__PURE__ */
|
|
2375
|
-
i > 0 && /* @__PURE__ */
|
|
2376
|
-
/* @__PURE__ */
|
|
2619
|
+
return /* @__PURE__ */ jsxs8(Box8, { children: [
|
|
2620
|
+
/* @__PURE__ */ jsx8(Text8, { color: isFocused ? theme.accent : void 0, bold: isFocused, children: label.padEnd(LABEL_WIDTH) }),
|
|
2621
|
+
isDriver ? /* @__PURE__ */ jsx8(Box8, { children: DRIVERS.map((d, i) => /* @__PURE__ */ jsxs8(Box8, { children: [
|
|
2622
|
+
i > 0 && /* @__PURE__ */ jsx8(Text8, { children: " " }),
|
|
2623
|
+
/* @__PURE__ */ jsxs8(Text8, { color: fields.driver === d ? theme.accent : void 0, bold: fields.driver === d, children: [
|
|
2377
2624
|
fields.driver === d ? "\u25CF " : "\u25CB ",
|
|
2378
2625
|
d.charAt(0).toUpperCase() + d.slice(1)
|
|
2379
2626
|
] })
|
|
2380
|
-
] }, d)) }) : /* @__PURE__ */
|
|
2381
|
-
/* @__PURE__ */
|
|
2382
|
-
isFocused && /* @__PURE__ */
|
|
2383
|
-
isFocused && isPassword(idx) && keychainHint && /* @__PURE__ */
|
|
2627
|
+
] }, d)) }) : /* @__PURE__ */ jsxs8(Box8, { children: [
|
|
2628
|
+
/* @__PURE__ */ jsx8(Text8, { children: isPassword(idx) ? "\u2022".repeat(getTextValue(idx).length) : getTextValue(idx) }),
|
|
2629
|
+
isFocused && /* @__PURE__ */ jsx8(Text8, { color: theme.accent, bold: true, children: "\u258C" }),
|
|
2630
|
+
isFocused && isPassword(idx) && keychainHint && /* @__PURE__ */ jsx8(Text8, { dimColor: true, children: " (from keychain)" })
|
|
2384
2631
|
] })
|
|
2385
2632
|
] }, label);
|
|
2386
2633
|
}),
|
|
2387
|
-
/* @__PURE__ */
|
|
2634
|
+
/* @__PURE__ */ jsx8(Box8, { marginTop: 1, children: connecting ? /* @__PURE__ */ jsx8(Text8, { dimColor: true, children: "Connecting\u2026" }) : error ? /* @__PURE__ */ jsxs8(Text8, { color: theme.error, children: [
|
|
2388
2635
|
"\u2717 ",
|
|
2389
2636
|
error
|
|
2390
2637
|
] }) : null }),
|
|
2391
|
-
/* @__PURE__ */
|
|
2638
|
+
/* @__PURE__ */ jsx8(Box8, { marginTop: 1, children: /* @__PURE__ */ jsx8(Text8, { dimColor: true, children: "Tab \xB7 \u2191\u2193 navigate Enter connect \u2190\u2192 cycle driver" }) })
|
|
2392
2639
|
] });
|
|
2393
2640
|
}
|
|
2394
2641
|
|
|
2395
2642
|
// src/index.tsx
|
|
2396
|
-
import { jsx as
|
|
2643
|
+
import { jsx as jsx9 } from "react/jsx-runtime";
|
|
2397
2644
|
var { values } = parseArgs({
|
|
2398
2645
|
args: process.argv.slice(2).filter((a) => a !== "--"),
|
|
2399
2646
|
options: {
|
|
@@ -2423,10 +2670,10 @@ function Root({ initialDsn: initialDsn2, aiUrl: aiUrl2, aiModel: aiModel2, aiKey
|
|
|
2423
2670
|
}
|
|
2424
2671
|
}, []);
|
|
2425
2672
|
if (initialDsn2 && !connectionState && !dsnError) {
|
|
2426
|
-
return /* @__PURE__ */
|
|
2673
|
+
return /* @__PURE__ */ jsx9(Box9, { paddingX: 2, paddingTop: 1, children: /* @__PURE__ */ jsx9(Text9, { dimColor: true, children: "Connecting\u2026" }) });
|
|
2427
2674
|
}
|
|
2428
2675
|
if (!connectionState) {
|
|
2429
|
-
return /* @__PURE__ */
|
|
2676
|
+
return /* @__PURE__ */ jsx9(ConnectionWizard, { onConnect: setConnectionState, initialError: dsnError ?? void 0 });
|
|
2430
2677
|
}
|
|
2431
2678
|
async function handleChangeDatabase(database) {
|
|
2432
2679
|
if (connectionState.status !== "connected") return;
|
|
@@ -2436,7 +2683,7 @@ function Root({ initialDsn: initialDsn2, aiUrl: aiUrl2, aiModel: aiModel2, aiKey
|
|
|
2436
2683
|
const next = await connectParams({ ...current.params, database });
|
|
2437
2684
|
setConnectionState(next);
|
|
2438
2685
|
}
|
|
2439
|
-
return /* @__PURE__ */
|
|
2686
|
+
return /* @__PURE__ */ jsx9(
|
|
2440
2687
|
App,
|
|
2441
2688
|
{
|
|
2442
2689
|
connectionState,
|
|
@@ -2450,6 +2697,6 @@ function Root({ initialDsn: initialDsn2, aiUrl: aiUrl2, aiModel: aiModel2, aiKey
|
|
|
2450
2697
|
);
|
|
2451
2698
|
}
|
|
2452
2699
|
render(
|
|
2453
|
-
/* @__PURE__ */
|
|
2700
|
+
/* @__PURE__ */ jsx9(Root, { initialDsn, aiUrl, aiModel, aiKey }),
|
|
2454
2701
|
{ exitOnCtrlC: true }
|
|
2455
2702
|
);
|