midql-cli 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +152 -0
- package/dist/App-W6AAY42M.js +568 -0
- package/dist/chunk-3CIK5I4M.js +396 -0
- package/dist/chunk-3FPZQHHV.js +77 -0
- package/dist/chunk-3QAR6XBK.js +200 -0
- package/dist/chunk-7WNCISNV.js +106 -0
- package/dist/chunk-ATZPL4EX.js +35 -0
- package/dist/chunk-BAAEZXFH.js +73 -0
- package/dist/chunk-GWY47EDH.js +11 -0
- package/dist/chunk-NCN3ZBOJ.js +93 -0
- package/dist/chunk-PXDMSYWH.js +18 -0
- package/dist/chunk-TLTYYFT5.js +237 -0
- package/dist/chunk-VFC3HWTF.js +52 -0
- package/dist/chunk-WN6T44OZ.js +1643 -0
- package/dist/chunk-XJAN6OQ7.js +25 -0
- package/dist/cli.js +101 -0
- package/dist/controller-HDNDKU6K.js +18 -0
- package/dist/csv-F2MFR3XB.js +7 -0
- package/dist/dump-LQ7ULAAL.js +11 -0
- package/dist/execute-E45HSHNE.js +155 -0
- package/dist/humanize-UG6KBAFO.js +100 -0
- package/dist/index.d.ts +185 -0
- package/dist/index.js +55 -0
- package/dist/json-Z56V7OEB.js +7 -0
- package/dist/maintenance-QHIUTVV3.js +49 -0
- package/dist/meta-NZ5Z6NYN.js +369 -0
- package/dist/mysql-GQSXUUEK.js +204 -0
- package/dist/profiles-4QGMCJAO.js +67 -0
- package/dist/query-RCUXCOAF.js +122 -0
- package/dist/restore-4QXG2WRR.js +9 -0
- package/package.json +74 -0
|
@@ -0,0 +1,568 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
buildQuery,
|
|
4
|
+
dialectFor
|
|
5
|
+
} from "./chunk-TLTYYFT5.js";
|
|
6
|
+
import {
|
|
7
|
+
buildFkGraph,
|
|
8
|
+
dropFillers,
|
|
9
|
+
extractClauses,
|
|
10
|
+
findTable,
|
|
11
|
+
hasSqlShape,
|
|
12
|
+
startsWithSqlKeyword,
|
|
13
|
+
tokenize,
|
|
14
|
+
translate
|
|
15
|
+
} from "./chunk-WN6T44OZ.js";
|
|
16
|
+
import "./chunk-3CIK5I4M.js";
|
|
17
|
+
import "./chunk-7WNCISNV.js";
|
|
18
|
+
import "./chunk-3QAR6XBK.js";
|
|
19
|
+
import "./chunk-PXDMSYWH.js";
|
|
20
|
+
import {
|
|
21
|
+
AmbiguityError,
|
|
22
|
+
MidqlError,
|
|
23
|
+
ParseError
|
|
24
|
+
} from "./chunk-VFC3HWTF.js";
|
|
25
|
+
|
|
26
|
+
// src/repl/App.tsx
|
|
27
|
+
import { Box, Static, Text as Text2, useApp } from "ink";
|
|
28
|
+
import { useCallback, useEffect, useMemo, useRef, useState as useState2 } from "react";
|
|
29
|
+
|
|
30
|
+
// src/repl/LineEditor.tsx
|
|
31
|
+
import { Text, useInput } from "ink";
|
|
32
|
+
import { useState } from "react";
|
|
33
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
34
|
+
function LineEditor({
|
|
35
|
+
promptText,
|
|
36
|
+
promptColor,
|
|
37
|
+
disabled,
|
|
38
|
+
history,
|
|
39
|
+
onSubmit,
|
|
40
|
+
onExitRequest,
|
|
41
|
+
onChange,
|
|
42
|
+
onTab,
|
|
43
|
+
onCtrlR,
|
|
44
|
+
onEscape
|
|
45
|
+
}) {
|
|
46
|
+
const [buffer, setBuffer] = useState("");
|
|
47
|
+
const [cursor, setCursor] = useState(0);
|
|
48
|
+
const [historyIndex, setHistoryIndex] = useState(null);
|
|
49
|
+
const [draft, setDraft] = useState("");
|
|
50
|
+
const update = (nextBuffer, nextCursor) => {
|
|
51
|
+
setBuffer(nextBuffer);
|
|
52
|
+
setCursor(Math.max(0, Math.min(nextCursor, nextBuffer.length)));
|
|
53
|
+
onChange?.(nextBuffer);
|
|
54
|
+
};
|
|
55
|
+
useInput(
|
|
56
|
+
(input, key) => {
|
|
57
|
+
if (key.return) {
|
|
58
|
+
const value = buffer;
|
|
59
|
+
update("", 0);
|
|
60
|
+
setHistoryIndex(null);
|
|
61
|
+
setDraft("");
|
|
62
|
+
onSubmit(value);
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
if (key.ctrl && input === "c") {
|
|
66
|
+
if (buffer.length === 0) {
|
|
67
|
+
onExitRequest();
|
|
68
|
+
} else {
|
|
69
|
+
update("", 0);
|
|
70
|
+
setHistoryIndex(null);
|
|
71
|
+
}
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
if (key.ctrl && input === "d") {
|
|
75
|
+
onExitRequest();
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
if (key.ctrl && input === "u") {
|
|
79
|
+
update("", 0);
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
if (key.ctrl && input === "r") {
|
|
83
|
+
onCtrlR?.();
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
if (key.ctrl && input === "a") {
|
|
87
|
+
setCursor(0);
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
if (key.ctrl && input === "e") {
|
|
91
|
+
setCursor(buffer.length);
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
if (key.backspace || key.delete) {
|
|
95
|
+
if (cursor > 0) {
|
|
96
|
+
update(buffer.slice(0, cursor - 1) + buffer.slice(cursor), cursor - 1);
|
|
97
|
+
}
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
if (key.leftArrow) {
|
|
101
|
+
setCursor(Math.max(0, cursor - 1));
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
if (key.rightArrow) {
|
|
105
|
+
setCursor(Math.min(buffer.length, cursor + 1));
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
if (key.upArrow) {
|
|
109
|
+
if (history.length === 0) {
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
const nextIndex = historyIndex === null ? history.length - 1 : Math.max(0, historyIndex - 1);
|
|
113
|
+
if (historyIndex === null) {
|
|
114
|
+
setDraft(buffer);
|
|
115
|
+
}
|
|
116
|
+
setHistoryIndex(nextIndex);
|
|
117
|
+
const entry = history[nextIndex] ?? "";
|
|
118
|
+
update(entry, entry.length);
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
if (key.downArrow) {
|
|
122
|
+
if (historyIndex === null) {
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
if (historyIndex >= history.length - 1) {
|
|
126
|
+
setHistoryIndex(null);
|
|
127
|
+
update(draft, draft.length);
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
const nextIndex = historyIndex + 1;
|
|
131
|
+
setHistoryIndex(nextIndex);
|
|
132
|
+
const entry = history[nextIndex] ?? "";
|
|
133
|
+
update(entry, entry.length);
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
if (key.tab) {
|
|
137
|
+
if (onTab) {
|
|
138
|
+
const completed = onTab(buffer);
|
|
139
|
+
if (completed !== null) {
|
|
140
|
+
update(completed, completed.length);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
if (key.escape) {
|
|
146
|
+
onEscape?.();
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
if (input && !key.ctrl && !key.meta) {
|
|
150
|
+
const clean = input.replace(/[\r\n]+/g, " ");
|
|
151
|
+
update(buffer.slice(0, cursor) + clean + buffer.slice(cursor), cursor + clean.length);
|
|
152
|
+
}
|
|
153
|
+
},
|
|
154
|
+
{ isActive: !disabled }
|
|
155
|
+
);
|
|
156
|
+
const before = buffer.slice(0, cursor);
|
|
157
|
+
const at = buffer[cursor] ?? " ";
|
|
158
|
+
const after = buffer.slice(cursor + 1);
|
|
159
|
+
return /* @__PURE__ */ jsxs(Text, { children: [
|
|
160
|
+
/* @__PURE__ */ jsx(Text, { color: promptColor, bold: true, children: promptText }),
|
|
161
|
+
/* @__PURE__ */ jsx(Text, { children: before }),
|
|
162
|
+
/* @__PURE__ */ jsx(Text, { inverse: true, children: at }),
|
|
163
|
+
/* @__PURE__ */ jsx(Text, { children: after })
|
|
164
|
+
] });
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// src/repl/completion.ts
|
|
168
|
+
var intentVerbs = [
|
|
169
|
+
{ text: "show", detail: "select rows" },
|
|
170
|
+
{ text: "find", detail: "select with a filter" },
|
|
171
|
+
{ text: "count", detail: "count rows" },
|
|
172
|
+
{ text: "average", detail: "aggregate a column" },
|
|
173
|
+
{ text: "sum", detail: "aggregate a column" },
|
|
174
|
+
{ text: "add", detail: "insert a row" },
|
|
175
|
+
{ text: "update", detail: "modify rows" },
|
|
176
|
+
{ text: "delete", detail: "remove rows" },
|
|
177
|
+
{ text: "describe", detail: "table structure" },
|
|
178
|
+
{ text: "create table", detail: "new table" },
|
|
179
|
+
{ text: "drop table", detail: "remove a table" },
|
|
180
|
+
{ text: "show tables", detail: "list tables" }
|
|
181
|
+
];
|
|
182
|
+
var connectorSuggestions = [
|
|
183
|
+
{ text: "which has", detail: "add a filter" },
|
|
184
|
+
{ text: "where", detail: "add a filter" },
|
|
185
|
+
{ text: "sorted by", detail: "order results" },
|
|
186
|
+
{ text: "first 10", detail: "limit results" }
|
|
187
|
+
];
|
|
188
|
+
var operatorsByKind = {
|
|
189
|
+
number: [
|
|
190
|
+
{ text: ">", detail: "greater than" },
|
|
191
|
+
{ text: "<", detail: "less than" },
|
|
192
|
+
{ text: "between", detail: "range" },
|
|
193
|
+
{ text: "is", detail: "equals" }
|
|
194
|
+
],
|
|
195
|
+
text: [
|
|
196
|
+
{ text: "is", detail: "equals" },
|
|
197
|
+
{ text: "contains", detail: "substring match" },
|
|
198
|
+
{ text: "starts with", detail: "prefix match" },
|
|
199
|
+
{ text: "is null", detail: "missing value" }
|
|
200
|
+
],
|
|
201
|
+
boolean: [
|
|
202
|
+
{ text: "is true", detail: "" },
|
|
203
|
+
{ text: "is false", detail: "" }
|
|
204
|
+
],
|
|
205
|
+
date: [
|
|
206
|
+
{ text: ">", detail: "after" },
|
|
207
|
+
{ text: "<", detail: "before" },
|
|
208
|
+
{ text: "between", detail: "range" }
|
|
209
|
+
]
|
|
210
|
+
};
|
|
211
|
+
var sqlKeywords = [
|
|
212
|
+
"SELECT",
|
|
213
|
+
"FROM",
|
|
214
|
+
"WHERE",
|
|
215
|
+
"JOIN",
|
|
216
|
+
"LEFT JOIN",
|
|
217
|
+
"GROUP BY",
|
|
218
|
+
"ORDER BY",
|
|
219
|
+
"LIMIT",
|
|
220
|
+
"INSERT INTO",
|
|
221
|
+
"VALUES",
|
|
222
|
+
"UPDATE",
|
|
223
|
+
"SET",
|
|
224
|
+
"DELETE FROM",
|
|
225
|
+
"AND",
|
|
226
|
+
"OR",
|
|
227
|
+
"BEGIN",
|
|
228
|
+
"COMMIT",
|
|
229
|
+
"ROLLBACK"
|
|
230
|
+
];
|
|
231
|
+
function prefixFilter(suggestions, prefix) {
|
|
232
|
+
if (!prefix) {
|
|
233
|
+
return suggestions;
|
|
234
|
+
}
|
|
235
|
+
const lower = prefix.toLowerCase();
|
|
236
|
+
return suggestions.filter((suggestion) => suggestion.text.toLowerCase().startsWith(lower));
|
|
237
|
+
}
|
|
238
|
+
function tableSuggestions(schema) {
|
|
239
|
+
return [...schema.tables].sort((a, b) => b.approxRowCount - a.approxRowCount).map((table) => ({ text: table.name, detail: `~${table.approxRowCount} rows` }));
|
|
240
|
+
}
|
|
241
|
+
function columnSuggestions(base, schema) {
|
|
242
|
+
const own = base.columns.map((column) => ({
|
|
243
|
+
text: column.name,
|
|
244
|
+
detail: column.dataType
|
|
245
|
+
}));
|
|
246
|
+
const graph = buildFkGraph(schema);
|
|
247
|
+
const reachable = new Set((graph.get(base.name) ?? []).map((edge) => edge.to));
|
|
248
|
+
const foreign = [];
|
|
249
|
+
for (const table of schema.tables) {
|
|
250
|
+
if (table.name === base.name || !reachable.has(table.name)) {
|
|
251
|
+
continue;
|
|
252
|
+
}
|
|
253
|
+
for (const column of table.columns) {
|
|
254
|
+
if (column.isPrimaryKey || column.name.endsWith("_id")) {
|
|
255
|
+
continue;
|
|
256
|
+
}
|
|
257
|
+
foreign.push({ text: column.name, detail: `${table.name}, joined` });
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
return [...own, ...foreign];
|
|
261
|
+
}
|
|
262
|
+
function splitBuffer(buffer) {
|
|
263
|
+
const match = /^(.*?)([\w.]*)$/s.exec(buffer);
|
|
264
|
+
return { head: match?.[1] ?? buffer, partial: match?.[2] ?? "" };
|
|
265
|
+
}
|
|
266
|
+
function complete(buffer, schema, sqlLock) {
|
|
267
|
+
const { head, partial } = splitBuffer(buffer);
|
|
268
|
+
const apply = (suggestion) => `${head}${suggestion.text} `;
|
|
269
|
+
if (buffer.startsWith("\\") || !schema) {
|
|
270
|
+
return { suggestions: [], apply };
|
|
271
|
+
}
|
|
272
|
+
const rawSql = sqlLock || startsWithSqlKeyword(buffer) && hasSqlShape(buffer);
|
|
273
|
+
const suggestions = rawSql ? completeSql(buffer, partial, schema) : completeNl(buffer, partial, schema);
|
|
274
|
+
return { suggestions: suggestions.slice(0, 6), apply };
|
|
275
|
+
}
|
|
276
|
+
function completeNl(buffer, partial, schema) {
|
|
277
|
+
const tokens = dropFillers(tokenize(buffer));
|
|
278
|
+
const complete2 = partial.length === 0;
|
|
279
|
+
const settled = complete2 ? tokens : tokens.slice(0, -1);
|
|
280
|
+
if (settled.length === 0) {
|
|
281
|
+
return prefixFilter(intentVerbs, partial);
|
|
282
|
+
}
|
|
283
|
+
const clauses = extractClauses(settled.slice(1));
|
|
284
|
+
const subjectWords = clauses.subject.filter((token) => token.type === "word");
|
|
285
|
+
const lastSubject = subjectWords[subjectWords.length - 1];
|
|
286
|
+
const baseTable = lastSubject ? findTable(lastSubject.value, schema) : null;
|
|
287
|
+
if (!baseTable) {
|
|
288
|
+
return prefixFilter(tableSuggestions(schema), partial);
|
|
289
|
+
}
|
|
290
|
+
const inConditions = clauses.conditionTokens.length > 0 || /\b(which|where|whose|that|has|have|with|having)\s*$/i.test(
|
|
291
|
+
buffer.slice(0, buffer.length - partial.length)
|
|
292
|
+
);
|
|
293
|
+
const inSort = /\b(sorted|ordered|sort|order)\s+by\s*[\w.]*$/i.test(buffer);
|
|
294
|
+
if (inSort) {
|
|
295
|
+
return prefixFilter(columnSuggestions(baseTable, schema), partial);
|
|
296
|
+
}
|
|
297
|
+
if (inConditions) {
|
|
298
|
+
const conditionWords = clauses.conditionTokens.filter((token) => token.type === "word");
|
|
299
|
+
const last = conditionWords[conditionWords.length - 1];
|
|
300
|
+
const lastIsColumn = last !== void 0 && (baseTable.columns.some((column) => column.name === last.value) || schema.tables.some((table) => table.columns.some((column) => column.name === last.value)));
|
|
301
|
+
if (lastIsColumn) {
|
|
302
|
+
const column = baseTable.columns.find((entry) => entry.name === last.value) ?? schema.tables.flatMap((table) => table.columns).find((entry) => entry.name === last.value);
|
|
303
|
+
const kind = column?.kind ?? "text";
|
|
304
|
+
return prefixFilter(operatorsByKind[kind] ?? operatorsByKind.text, partial);
|
|
305
|
+
}
|
|
306
|
+
return prefixFilter(columnSuggestions(baseTable, schema), partial);
|
|
307
|
+
}
|
|
308
|
+
return prefixFilter(connectorSuggestions, partial);
|
|
309
|
+
}
|
|
310
|
+
function completeSql(buffer, partial, schema) {
|
|
311
|
+
const before = buffer.slice(0, buffer.length - partial.length);
|
|
312
|
+
const context = /\b(from|join|into|update|table)\s+$/i.test(before) ? "table" : /\b(select|where|set|by|and|or|on)\s+$/i.test(before) ? "column" : "keyword";
|
|
313
|
+
if (context === "table") {
|
|
314
|
+
return prefixFilter(tableSuggestions(schema), partial);
|
|
315
|
+
}
|
|
316
|
+
if (context === "column") {
|
|
317
|
+
const columns = schema.tables.flatMap(
|
|
318
|
+
(table) => table.columns.map((column) => ({ text: column.name, detail: table.name }))
|
|
319
|
+
);
|
|
320
|
+
return prefixFilter(columns, partial);
|
|
321
|
+
}
|
|
322
|
+
const keywords = sqlKeywords.map((keyword) => ({ text: keyword, detail: "" }));
|
|
323
|
+
return prefixFilter([...keywords, ...tableSuggestions(schema)], partial);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// src/repl/livePreview.ts
|
|
327
|
+
function livePreview(buffer, schema, dialect, sqlLock, maxJoinDepth) {
|
|
328
|
+
const input = buffer.trim();
|
|
329
|
+
if (!input || input.startsWith("\\") || !schema) {
|
|
330
|
+
return { kind: "none" };
|
|
331
|
+
}
|
|
332
|
+
if (sqlLock || startsWithSqlKeyword(input) && hasSqlShape(input)) {
|
|
333
|
+
return { kind: "raw" };
|
|
334
|
+
}
|
|
335
|
+
try {
|
|
336
|
+
const ast = translate(input, schema, { maxJoinDepth });
|
|
337
|
+
if (ast.kind === "showTables") {
|
|
338
|
+
return { kind: "hint", message: "lists all tables" };
|
|
339
|
+
}
|
|
340
|
+
if (ast.kind === "describe") {
|
|
341
|
+
return { kind: "hint", message: `shows the structure of ${ast.table}` };
|
|
342
|
+
}
|
|
343
|
+
const built = buildQuery(ast, dialectFor(dialect));
|
|
344
|
+
const params = built.params.length > 0 ? built.params.map((value, index) => `$${index + 1}=${JSON.stringify(value)}`).join(" ") : "";
|
|
345
|
+
return { kind: "sql", sql: built.sql, params };
|
|
346
|
+
} catch (error) {
|
|
347
|
+
if (error instanceof AmbiguityError) {
|
|
348
|
+
return {
|
|
349
|
+
kind: "ambiguity",
|
|
350
|
+
token: error.token,
|
|
351
|
+
options: error.candidates.map((candidate) => candidate.value)
|
|
352
|
+
};
|
|
353
|
+
}
|
|
354
|
+
if (error instanceof ParseError) {
|
|
355
|
+
const suggestion = error.suggestions[0] ? ` \u2014 try: ${error.suggestions.join(", ")}` : "";
|
|
356
|
+
return { kind: "hint", message: `${error.message}${suggestion}` };
|
|
357
|
+
}
|
|
358
|
+
if (error instanceof MidqlError) {
|
|
359
|
+
return { kind: "hint", message: error.message };
|
|
360
|
+
}
|
|
361
|
+
return { kind: "none" };
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
// src/repl/App.tsx
|
|
366
|
+
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
367
|
+
var blockColors = {
|
|
368
|
+
info: "cyan",
|
|
369
|
+
warn: "yellow",
|
|
370
|
+
error: "red",
|
|
371
|
+
success: "green",
|
|
372
|
+
sql: "gray",
|
|
373
|
+
plain: void 0,
|
|
374
|
+
table: void 0,
|
|
375
|
+
echo: "gray"
|
|
376
|
+
};
|
|
377
|
+
function dialectColor(dialect, env) {
|
|
378
|
+
if (env === "prod") {
|
|
379
|
+
return "red";
|
|
380
|
+
}
|
|
381
|
+
if (dialect === "mysql") {
|
|
382
|
+
return "yellow";
|
|
383
|
+
}
|
|
384
|
+
if (dialect === "postgres") {
|
|
385
|
+
return "blue";
|
|
386
|
+
}
|
|
387
|
+
return "gray";
|
|
388
|
+
}
|
|
389
|
+
function App({ controller, version, initialTarget }) {
|
|
390
|
+
const { exit } = useApp();
|
|
391
|
+
const [items, setItems] = useState2([]);
|
|
392
|
+
const [busy, setBusy] = useState2(false);
|
|
393
|
+
const [buffer, setBuffer] = useState2("");
|
|
394
|
+
const [searchMode, setSearchMode] = useState2(false);
|
|
395
|
+
const [, setTick] = useState2(0);
|
|
396
|
+
const nextId = useRef(0);
|
|
397
|
+
const append = useCallback((blocks) => {
|
|
398
|
+
setItems((current) => [
|
|
399
|
+
...current,
|
|
400
|
+
...blocks.map((entry) => ({
|
|
401
|
+
id: nextId.current++,
|
|
402
|
+
kind: entry.kind,
|
|
403
|
+
text: entry.text
|
|
404
|
+
}))
|
|
405
|
+
]);
|
|
406
|
+
}, []);
|
|
407
|
+
const runInput = useCallback(
|
|
408
|
+
async (value, echoPrefix) => {
|
|
409
|
+
setBuffer("");
|
|
410
|
+
setSearchMode(false);
|
|
411
|
+
if (value.trim().length > 0) {
|
|
412
|
+
append([{ id: 0, kind: "echo", text: `${echoPrefix}${value}` }]);
|
|
413
|
+
}
|
|
414
|
+
setBusy(true);
|
|
415
|
+
const result = await controller.handleInput(value);
|
|
416
|
+
append(result.blocks);
|
|
417
|
+
setBusy(false);
|
|
418
|
+
setTick((tick) => tick + 1);
|
|
419
|
+
if (result.exit) {
|
|
420
|
+
exit();
|
|
421
|
+
}
|
|
422
|
+
},
|
|
423
|
+
[append, controller, exit]
|
|
424
|
+
);
|
|
425
|
+
useEffect(() => {
|
|
426
|
+
append([
|
|
427
|
+
{
|
|
428
|
+
id: 0,
|
|
429
|
+
kind: "plain",
|
|
430
|
+
text: `midql v${version} \u2014 plain English or raw SQL \xB7 \\help for commands \xB7 \\q to quit`
|
|
431
|
+
}
|
|
432
|
+
]);
|
|
433
|
+
if (initialTarget) {
|
|
434
|
+
void runInput(`\\c ${initialTarget}`, "");
|
|
435
|
+
}
|
|
436
|
+
}, [append, initialTarget, runInput, version]);
|
|
437
|
+
const info = controller.promptInfo();
|
|
438
|
+
const pending = controller.pendingConfirmation();
|
|
439
|
+
const connection = controller.manager.active();
|
|
440
|
+
const schema = connection?.schema ?? null;
|
|
441
|
+
const suggestions = useMemo(() => {
|
|
442
|
+
if (pending || busy) {
|
|
443
|
+
return [];
|
|
444
|
+
}
|
|
445
|
+
if (searchMode) {
|
|
446
|
+
const needle = buffer.toLowerCase();
|
|
447
|
+
return controller.inputHistory.filter((entry, index, all) => all.lastIndexOf(entry) === index).filter((entry) => entry.toLowerCase().includes(needle)).slice(-6).reverse().map((entry) => ({ text: entry, detail: "history" }));
|
|
448
|
+
}
|
|
449
|
+
if (!buffer.trim()) {
|
|
450
|
+
return [];
|
|
451
|
+
}
|
|
452
|
+
return complete(buffer, schema, controller.sqlLock).suggestions;
|
|
453
|
+
}, [buffer, busy, controller, pending, schema, searchMode]);
|
|
454
|
+
const preview = useMemo(() => {
|
|
455
|
+
if (pending || busy || searchMode || !connection) {
|
|
456
|
+
return { kind: "none" };
|
|
457
|
+
}
|
|
458
|
+
return livePreview(
|
|
459
|
+
buffer,
|
|
460
|
+
schema,
|
|
461
|
+
connection.dialect,
|
|
462
|
+
controller.sqlLock,
|
|
463
|
+
controller.config.maxJoinDepth
|
|
464
|
+
);
|
|
465
|
+
}, [buffer, busy, connection, controller, pending, schema, searchMode]);
|
|
466
|
+
const handleTab = useCallback(
|
|
467
|
+
(current) => {
|
|
468
|
+
if (searchMode) {
|
|
469
|
+
const match = suggestions[0];
|
|
470
|
+
if (match) {
|
|
471
|
+
setSearchMode(false);
|
|
472
|
+
return match.text;
|
|
473
|
+
}
|
|
474
|
+
return null;
|
|
475
|
+
}
|
|
476
|
+
const result = complete(current, schema, controller.sqlLock);
|
|
477
|
+
const top = result.suggestions[0];
|
|
478
|
+
if (!top) {
|
|
479
|
+
return null;
|
|
480
|
+
}
|
|
481
|
+
return result.apply(top);
|
|
482
|
+
},
|
|
483
|
+
[controller, schema, searchMode, suggestions]
|
|
484
|
+
);
|
|
485
|
+
const handleExitRequest = useCallback(() => {
|
|
486
|
+
void controller.shutdown().then((blocks) => {
|
|
487
|
+
append(blocks);
|
|
488
|
+
exit();
|
|
489
|
+
});
|
|
490
|
+
}, [append, controller, exit]);
|
|
491
|
+
let promptText;
|
|
492
|
+
let promptColor;
|
|
493
|
+
if (pending) {
|
|
494
|
+
promptText = `${pending.prompt}> `;
|
|
495
|
+
promptColor = "red";
|
|
496
|
+
} else if (searchMode) {
|
|
497
|
+
promptText = "(history search)> ";
|
|
498
|
+
promptColor = "magenta";
|
|
499
|
+
} else if (!info) {
|
|
500
|
+
promptText = "midql (not connected)> ";
|
|
501
|
+
promptColor = "gray";
|
|
502
|
+
} else {
|
|
503
|
+
const lock = info.sqlLock ? " sql" : "";
|
|
504
|
+
const tx = info.inTransaction ? " [tx]" : "";
|
|
505
|
+
const ro = info.readonly ? " [ro]" : "";
|
|
506
|
+
const bang = info.env === "prod" ? "!" : "";
|
|
507
|
+
promptText = `midql${lock} (${info.alias}:${info.database})${tx}${ro}${bang}> `;
|
|
508
|
+
promptColor = dialectColor(info.dialect, info.env);
|
|
509
|
+
}
|
|
510
|
+
const connections = controller.manager.list();
|
|
511
|
+
const statusParts = connections.map(
|
|
512
|
+
(entry) => entry.alias === info?.alias ? `[${entry.alias}:${entry.env}]` : `${entry.alias}:${entry.env}`
|
|
513
|
+
);
|
|
514
|
+
return /* @__PURE__ */ jsxs2(Box, { flexDirection: "column", children: [
|
|
515
|
+
/* @__PURE__ */ jsx2(Static, { items, children: (item) => /* @__PURE__ */ jsx2(Box, { flexDirection: "column", children: /* @__PURE__ */ jsx2(Text2, { color: blockColors[item.kind], children: item.text }) }, item.id) }),
|
|
516
|
+
busy ? /* @__PURE__ */ jsx2(Text2, { color: "cyan", children: "\u2026 running" }) : /* @__PURE__ */ jsx2(
|
|
517
|
+
LineEditor,
|
|
518
|
+
{
|
|
519
|
+
promptText,
|
|
520
|
+
promptColor,
|
|
521
|
+
disabled: busy,
|
|
522
|
+
history: controller.inputHistory,
|
|
523
|
+
onSubmit: (value) => void runInput(value, promptText),
|
|
524
|
+
onExitRequest: handleExitRequest,
|
|
525
|
+
onChange: setBuffer,
|
|
526
|
+
onTab: handleTab,
|
|
527
|
+
onCtrlR: () => setSearchMode((mode) => !mode),
|
|
528
|
+
onEscape: () => setSearchMode(false)
|
|
529
|
+
}
|
|
530
|
+
),
|
|
531
|
+
preview.kind === "sql" && /* @__PURE__ */ jsxs2(Text2, { dimColor: true, children: [
|
|
532
|
+
"\u2192 ",
|
|
533
|
+
preview.sql,
|
|
534
|
+
preview.params ? ` ${preview.params}` : ""
|
|
535
|
+
] }),
|
|
536
|
+
preview.kind === "hint" && /* @__PURE__ */ jsxs2(Text2, { dimColor: true, children: [
|
|
537
|
+
"\u2026 ",
|
|
538
|
+
preview.message
|
|
539
|
+
] }),
|
|
540
|
+
preview.kind === "raw" && /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: "\u2192 raw SQL, runs as written" }),
|
|
541
|
+
preview.kind === "ambiguity" && /* @__PURE__ */ jsxs2(Text2, { color: "yellow", children: [
|
|
542
|
+
'"',
|
|
543
|
+
preview.token,
|
|
544
|
+
'"? ',
|
|
545
|
+
preview.options.join(" \xB7 ")
|
|
546
|
+
] }),
|
|
547
|
+
suggestions.length > 0 && /* @__PURE__ */ jsx2(Text2, { children: suggestions.map((suggestion, index) => /* @__PURE__ */ jsxs2(
|
|
548
|
+
Text2,
|
|
549
|
+
{
|
|
550
|
+
color: index === 0 ? "cyan" : void 0,
|
|
551
|
+
dimColor: index !== 0,
|
|
552
|
+
children: [
|
|
553
|
+
index > 0 ? " " : "",
|
|
554
|
+
suggestion.text,
|
|
555
|
+
suggestion.detail ? ` (${suggestion.detail})` : ""
|
|
556
|
+
]
|
|
557
|
+
},
|
|
558
|
+
suggestion.text
|
|
559
|
+
)) }),
|
|
560
|
+
/* @__PURE__ */ jsxs2(Text2, { dimColor: true, children: [
|
|
561
|
+
connections.length > 0 ? `${statusParts.join(" ")} ` : "",
|
|
562
|
+
"Tab completes \xB7 Ctrl+R history \xB7 \\help"
|
|
563
|
+
] })
|
|
564
|
+
] });
|
|
565
|
+
}
|
|
566
|
+
export {
|
|
567
|
+
App
|
|
568
|
+
};
|