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.
@@ -0,0 +1,49 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ backupDatabase,
4
+ defaultBackupFile
5
+ } from "./chunk-BAAEZXFH.js";
6
+ import {
7
+ restoreDatabase
8
+ } from "./chunk-3FPZQHHV.js";
9
+ import "./chunk-NCN3ZBOJ.js";
10
+ import {
11
+ resolveTarget
12
+ } from "./chunk-3CIK5I4M.js";
13
+ import {
14
+ loadConfig
15
+ } from "./chunk-3QAR6XBK.js";
16
+ import "./chunk-PXDMSYWH.js";
17
+ import "./chunk-VFC3HWTF.js";
18
+
19
+ // src/commands/maintenance.ts
20
+ import pc from "picocolors";
21
+ async function runBackupCommand(file, options) {
22
+ const config = loadConfig();
23
+ const target = resolveTarget(options.db);
24
+ const format = options.sql || target.dialect === "mysql" ? "sql" : "custom";
25
+ const output = file ?? defaultBackupFile(target.options.database, target.dialect, format);
26
+ const result = await backupDatabase(target.dialect, target.options, output, format, config.tools);
27
+ const size = result.sizeBytes > 1024 * 1024 ? `${(result.sizeBytes / 1024 / 1024).toFixed(1)} MB` : `${Math.max(1, Math.round(result.sizeBytes / 1024))} KB`;
28
+ console.log(pc.green(`Backup written to ${result.file} (${size}).`));
29
+ return 0;
30
+ }
31
+ async function runRestoreCommand(file, options) {
32
+ const config = loadConfig();
33
+ const target = resolveTarget(options.db);
34
+ if (!options.yes) {
35
+ console.error(
36
+ pc.yellow(
37
+ `Restoring will overwrite objects in "${target.options.database}". Re-run with --yes to proceed.`
38
+ )
39
+ );
40
+ return 1;
41
+ }
42
+ await restoreDatabase(target.dialect, target.options, file, config.tools);
43
+ console.log(pc.green(`Restored ${target.options.database} from ${file}.`));
44
+ return 0;
45
+ }
46
+ export {
47
+ runBackupCommand,
48
+ runRestoreCommand
49
+ };
@@ -0,0 +1,369 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ block,
4
+ loadHistory,
5
+ searchHistory
6
+ } from "./chunk-WN6T44OZ.js";
7
+ import "./chunk-3CIK5I4M.js";
8
+ import {
9
+ describeTable,
10
+ formatTableList,
11
+ plainTable
12
+ } from "./chunk-7WNCISNV.js";
13
+ import "./chunk-3QAR6XBK.js";
14
+ import "./chunk-PXDMSYWH.js";
15
+ import "./chunk-VFC3HWTF.js";
16
+
17
+ // src/repl/meta.ts
18
+ var commands = {
19
+ help: {
20
+ usage: "\\help",
21
+ description: "Show available commands",
22
+ run() {
23
+ const rows = Object.entries(commands).map(([, command]) => [
24
+ command.usage,
25
+ command.description
26
+ ]);
27
+ return { blocks: [block("table", plainTable(["command", "description"], rows))] };
28
+ }
29
+ },
30
+ c: {
31
+ usage: "\\c <profile|url> [alias]",
32
+ description: "Connect to a database (saved profile name or postgres://... / mysql://... URL)",
33
+ async run(controller, args) {
34
+ const target = args[0];
35
+ if (!target) {
36
+ return { blocks: [block("error", "Usage: \\c <profile|url> [alias]")] };
37
+ }
38
+ const connection = await controller.manager.connect(target, args[1]);
39
+ const envNote = connection.env === "prod" ? " [PRODUCTION]" : "";
40
+ return {
41
+ blocks: [
42
+ block(
43
+ "success",
44
+ `Connected to ${connection.dialect}://${connection.database} as "${connection.alias}"${envNote} \xB7 ${connection.schema.tables.length} tables`
45
+ )
46
+ ]
47
+ };
48
+ }
49
+ },
50
+ use: {
51
+ usage: "\\use <alias>",
52
+ description: "Switch the active connection",
53
+ run(controller, args) {
54
+ const alias = args[0];
55
+ if (!alias) {
56
+ return { blocks: [block("error", "Usage: \\use <alias>")] };
57
+ }
58
+ const connection = controller.manager.use(alias);
59
+ return {
60
+ blocks: [block("success", `Switched to "${connection.alias}" (${connection.database})`)]
61
+ };
62
+ }
63
+ },
64
+ connections: {
65
+ usage: "\\connections",
66
+ description: "List open connections",
67
+ run(controller) {
68
+ const list = controller.manager.list();
69
+ if (list.length === 0) {
70
+ return { blocks: [block("info", "No open connections.")] };
71
+ }
72
+ const activeAlias = controller.manager.active()?.alias;
73
+ const rows = list.map((connection) => [
74
+ connection.alias === activeAlias ? `* ${connection.alias}` : ` ${connection.alias}`,
75
+ connection.dialect,
76
+ connection.database,
77
+ connection.env,
78
+ connection.readonly ? "read-only" : "read-write",
79
+ connection.adapter.inTransaction() ? "open tx" : ""
80
+ ]);
81
+ return {
82
+ blocks: [
83
+ block("table", plainTable(["alias", "dialect", "database", "env", "mode", "tx"], rows))
84
+ ]
85
+ };
86
+ }
87
+ },
88
+ disconnect: {
89
+ usage: "\\disconnect <alias>",
90
+ description: "Close a connection",
91
+ async run(controller, args) {
92
+ const alias = args[0];
93
+ if (!alias) {
94
+ return { blocks: [block("error", "Usage: \\disconnect <alias>")] };
95
+ }
96
+ await controller.manager.disconnect(alias);
97
+ return { blocks: [block("success", `Disconnected "${alias}"`)] };
98
+ }
99
+ },
100
+ tables: {
101
+ usage: "\\tables",
102
+ description: "List tables in the active database",
103
+ run(controller) {
104
+ const connection = controller.manager.active();
105
+ if (!connection) {
106
+ return { blocks: [block("error", "Not connected.")] };
107
+ }
108
+ if (connection.schema.tables.length === 0) {
109
+ return { blocks: [block("info", "No tables found in the public schema.")] };
110
+ }
111
+ return { blocks: [block("table", formatTableList(connection.schema))] };
112
+ }
113
+ },
114
+ d: {
115
+ usage: "\\d <table>",
116
+ description: "Describe a table",
117
+ run(controller, args) {
118
+ const connection = controller.manager.active();
119
+ if (!connection) {
120
+ return { blocks: [block("error", "Not connected.")] };
121
+ }
122
+ const name = args[0];
123
+ if (!name) {
124
+ return { blocks: [block("error", "Usage: \\d <table>")] };
125
+ }
126
+ const table = connection.schema.tables.find((entry) => entry.name === name);
127
+ if (!table) {
128
+ return { blocks: [block("error", `Table "${name}" not found.`)] };
129
+ }
130
+ return { blocks: [block("table", describeTable(table))] };
131
+ }
132
+ },
133
+ refresh: {
134
+ usage: "\\refresh",
135
+ description: "Reload the schema cache for the active connection",
136
+ async run(controller) {
137
+ const connection = await controller.manager.refreshSchema();
138
+ return {
139
+ blocks: [block("success", `Schema refreshed \xB7 ${connection.schema.tables.length} tables`)]
140
+ };
141
+ }
142
+ },
143
+ sql: {
144
+ usage: "\\sql",
145
+ description: "Toggle raw SQL mode (skip natural language parsing)",
146
+ run(controller) {
147
+ controller.sqlLock = !controller.sqlLock;
148
+ return {
149
+ blocks: [
150
+ block(
151
+ "info",
152
+ controller.sqlLock ? "Raw SQL mode ON \u2014 input runs directly against the database." : "Raw SQL mode OFF \u2014 natural language parsing enabled."
153
+ )
154
+ ]
155
+ };
156
+ }
157
+ },
158
+ safe: {
159
+ usage: "\\safe on|off",
160
+ description: "Toggle destructive-operation previews and confirmations",
161
+ run(controller, args) {
162
+ const mode = args[0]?.toLowerCase();
163
+ if (mode !== "on" && mode !== "off") {
164
+ return { blocks: [block("error", "Usage: \\safe on|off")] };
165
+ }
166
+ controller.safety = mode;
167
+ const blocks = [block("info", `Safety checks ${mode.toUpperCase()}.`)];
168
+ if (mode === "off" && controller.manager.active()?.env === "prod") {
169
+ blocks.push(
170
+ block("warn", "Active connection is tagged prod \u2014 safety stays enforced there.")
171
+ );
172
+ }
173
+ return { blocks };
174
+ }
175
+ },
176
+ readonly: {
177
+ usage: "\\readonly on|off",
178
+ description: "Toggle read-only mode for the active connection",
179
+ run(controller, args) {
180
+ const mode = args[0]?.toLowerCase();
181
+ if (mode !== "on" && mode !== "off") {
182
+ return { blocks: [block("error", "Usage: \\readonly on|off")] };
183
+ }
184
+ const connection = controller.manager.setReadonly(mode === "on");
185
+ return {
186
+ blocks: [
187
+ block(
188
+ "info",
189
+ `"${connection.alias}" is now ${connection.readonly ? "read-only" : "read-write"}.`
190
+ )
191
+ ]
192
+ };
193
+ }
194
+ },
195
+ history: {
196
+ usage: "\\history [search]",
197
+ description: "Show recent inputs, optionally filtered",
198
+ run(_controller, args) {
199
+ const entries = loadHistory();
200
+ const filtered = args.length > 0 ? searchHistory(entries, args.join(" ")) : entries;
201
+ const recent = filtered.slice(-20);
202
+ if (recent.length === 0) {
203
+ return { blocks: [block("info", "No history yet.")] };
204
+ }
205
+ const rows = recent.map((entry) => [
206
+ entry.ts.replace("T", " ").slice(0, 19),
207
+ entry.connection,
208
+ entry.input.length > 60 ? `${entry.input.slice(0, 59)}\u2026` : entry.input,
209
+ entry.ok ? "ok" : "error"
210
+ ]);
211
+ return {
212
+ blocks: [block("table", plainTable(["time", "connection", "input", "status"], rows))]
213
+ };
214
+ }
215
+ },
216
+ export: {
217
+ usage: "\\export csv|json <file>",
218
+ description: "Export the last result set to a file",
219
+ async run(controller, args) {
220
+ const format = args[0]?.toLowerCase();
221
+ const file = args[1];
222
+ if (format !== "csv" && format !== "json" || !file) {
223
+ return { blocks: [block("error", "Usage: \\export csv|json <file>")] };
224
+ }
225
+ if (!controller.lastResult || controller.lastResult.columns.length === 0) {
226
+ return { blocks: [block("error", "No result set to export yet \u2014 run a query first.")] };
227
+ }
228
+ const { writeFileSync } = await import("fs");
229
+ const { toCsv } = await import("./csv-F2MFR3XB.js");
230
+ const { toJson } = await import("./json-Z56V7OEB.js");
231
+ const content = format === "csv" ? toCsv(controller.lastResult) : toJson(controller.lastResult);
232
+ writeFileSync(file, content, "utf8");
233
+ return {
234
+ blocks: [
235
+ block(
236
+ "success",
237
+ `Exported ${controller.lastResult.rows.length} rows to ${file} (${format}).`
238
+ )
239
+ ]
240
+ };
241
+ }
242
+ },
243
+ backup: {
244
+ usage: "\\backup [file] [sql]",
245
+ description: "Back up the active database via pg_dump / mysqldump",
246
+ async run(controller, args) {
247
+ const connection = controller.manager.active();
248
+ if (!connection) {
249
+ return { blocks: [block("error", "Not connected.")] };
250
+ }
251
+ const { backupDatabase, defaultBackupFile } = await import("./dump-LQ7ULAAL.js");
252
+ const format = args.includes("sql") || connection.dialect === "mysql" ? "sql" : "custom";
253
+ const file = args.find((arg) => arg !== "sql") ?? defaultBackupFile(connection.database, connection.dialect, format);
254
+ const result = await backupDatabase(
255
+ connection.dialect,
256
+ connection.options,
257
+ file,
258
+ format,
259
+ controller.config.tools
260
+ );
261
+ const size = result.sizeBytes > 1024 * 1024 ? `${(result.sizeBytes / 1024 / 1024).toFixed(1)} MB` : `${Math.max(1, Math.round(result.sizeBytes / 1024))} KB`;
262
+ return {
263
+ blocks: [block("success", `Backup written to ${result.file} (${size}).`)]
264
+ };
265
+ }
266
+ },
267
+ restore: {
268
+ usage: "\\restore <file>",
269
+ description: "Restore the active database from a backup file",
270
+ async run(controller, args) {
271
+ const connection = controller.manager.active();
272
+ if (!connection) {
273
+ return { blocks: [block("error", "Not connected.")] };
274
+ }
275
+ const file = args[0];
276
+ if (!file) {
277
+ return { blocks: [block("error", "Usage: \\restore <file>")] };
278
+ }
279
+ const prodBanner = connection.env === "prod" ? " (PRODUCTION)" : "";
280
+ controller.setPending({
281
+ prompt: `type '${connection.database}' to confirm`,
282
+ expectedText: connection.database,
283
+ async execute() {
284
+ const { restoreDatabase } = await import("./restore-4QXG2WRR.js");
285
+ await restoreDatabase(
286
+ connection.dialect,
287
+ connection.options,
288
+ file,
289
+ controller.config.tools
290
+ );
291
+ await controller.manager.refreshSchema(connection.alias).catch(() => connection);
292
+ return [block("success", `Restored ${connection.database} from ${file}.`)];
293
+ }
294
+ });
295
+ return {
296
+ blocks: [
297
+ block(
298
+ "error",
299
+ `\u26A0${prodBanner} Restoring will overwrite objects in "${connection.database}".`
300
+ ),
301
+ block("warn", `Type '${connection.database}' to confirm:`)
302
+ ]
303
+ };
304
+ }
305
+ },
306
+ explain: {
307
+ usage: "\\explain",
308
+ description: "Re-run the last query with EXPLAIN and summarize the plan in plain English",
309
+ async run(controller) {
310
+ const connection = controller.manager.active();
311
+ if (!connection) {
312
+ return { blocks: [block("error", "Not connected.")] };
313
+ }
314
+ if (!controller.lastSql || !/^\s*select\b/i.test(controller.lastSql)) {
315
+ return { blocks: [block("error", "No SELECT to explain yet \u2014 run a query first.")] };
316
+ }
317
+ const { humanizePostgresPlan, humanizeMysqlExplain } = await import("./humanize-UG6KBAFO.js");
318
+ if (connection.dialect === "postgres") {
319
+ const result2 = await connection.adapter.query(
320
+ `EXPLAIN (FORMAT JSON) ${controller.lastSql}`,
321
+ controller.lastParams
322
+ );
323
+ const planCell = Object.values(result2.rows[0] ?? {})[0];
324
+ const plan = typeof planCell === "string" ? JSON.parse(planCell) : planCell;
325
+ return { blocks: [block("info", humanizePostgresPlan(plan).join("\n"))] };
326
+ }
327
+ const result = await connection.adapter.query(
328
+ `EXPLAIN ${controller.lastSql}`,
329
+ controller.lastParams
330
+ );
331
+ return { blocks: [block("info", humanizeMysqlExplain(result.rows).join("\n"))] };
332
+ }
333
+ },
334
+ q: {
335
+ usage: "\\q",
336
+ description: "Quit midql",
337
+ async run(controller) {
338
+ const blocks = await controller.shutdown();
339
+ return { blocks, exit: true };
340
+ }
341
+ }
342
+ };
343
+ var aliases = {
344
+ connect: "c",
345
+ quit: "q",
346
+ exit: "q",
347
+ describe: "d",
348
+ ls: "tables",
349
+ h: "help"
350
+ };
351
+ async function dispatchMeta(controller, input) {
352
+ const parts = input.slice(1).trim().split(/\s+/);
353
+ const name = (parts[0] ?? "").toLowerCase();
354
+ const args = parts.slice(1);
355
+ const command = commands[name] ?? commands[aliases[name] ?? ""];
356
+ if (!command) {
357
+ return {
358
+ blocks: [block("error", `Unknown command \\${name}. Type \\help for the list.`)]
359
+ };
360
+ }
361
+ return command.run(controller, args);
362
+ }
363
+ function registerMetaCommand(name, command) {
364
+ commands[name] = command;
365
+ }
366
+ export {
367
+ dispatchMeta,
368
+ registerMetaCommand
369
+ };
@@ -0,0 +1,204 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ trackTransactionState
4
+ } from "./chunk-PXDMSYWH.js";
5
+ import {
6
+ ConnectionError,
7
+ ExecutionError
8
+ } from "./chunk-VFC3HWTF.js";
9
+
10
+ // src/adapters/introspect/mysql.ts
11
+ var mysqlColumnsQuery = `
12
+ SELECT TABLE_NAME AS table_name, COLUMN_NAME AS column_name, DATA_TYPE AS data_type,
13
+ IS_NULLABLE AS is_nullable, COLUMN_DEFAULT AS column_default, COLUMN_KEY AS column_key,
14
+ EXTRA AS extra
15
+ FROM information_schema.COLUMNS
16
+ WHERE TABLE_SCHEMA = DATABASE()
17
+ ORDER BY TABLE_NAME, ORDINAL_POSITION`;
18
+ var mysqlForeignKeysQuery = `
19
+ SELECT CONSTRAINT_NAME AS constraint_name, TABLE_NAME AS table_name, COLUMN_NAME AS column_name,
20
+ REFERENCED_TABLE_NAME AS referenced_table, REFERENCED_COLUMN_NAME AS referenced_column
21
+ FROM information_schema.KEY_COLUMN_USAGE
22
+ WHERE TABLE_SCHEMA = DATABASE() AND REFERENCED_TABLE_NAME IS NOT NULL
23
+ ORDER BY TABLE_NAME, CONSTRAINT_NAME, ORDINAL_POSITION`;
24
+ var mysqlRowCountsQuery = `
25
+ SELECT TABLE_NAME AS table_name, COALESCE(TABLE_ROWS, 0) AS approx_rows
26
+ FROM information_schema.TABLES
27
+ WHERE TABLE_SCHEMA = DATABASE() AND TABLE_TYPE = 'BASE TABLE'`;
28
+ function classifyMysqlType(dataType) {
29
+ const type = dataType.toLowerCase();
30
+ if (type.includes("int") || type.includes("decimal") || type.includes("float") || type.includes("double") || type.includes("numeric")) {
31
+ return "number";
32
+ }
33
+ if (type === "bit" || type === "boolean" || type === "bool" || type === "tinyint") {
34
+ return "boolean";
35
+ }
36
+ if (type.includes("date") || type.includes("time") || type === "year") {
37
+ return "date";
38
+ }
39
+ if (type.includes("json")) {
40
+ return "json";
41
+ }
42
+ if (type.includes("char") || type.includes("text") || type.includes("enum")) {
43
+ return "text";
44
+ }
45
+ return "other";
46
+ }
47
+ function assembleMysqlSchema(columnRows, foreignKeyRows, rowCountRows) {
48
+ const foreignKeys = /* @__PURE__ */ new Map();
49
+ for (const row of foreignKeyRows) {
50
+ const tableFks = foreignKeys.get(row.table_name) ?? /* @__PURE__ */ new Map();
51
+ const fk = tableFks.get(row.constraint_name) ?? {
52
+ columns: [],
53
+ referencedTable: row.referenced_table,
54
+ referencedColumns: []
55
+ };
56
+ fk.columns.push(row.column_name);
57
+ fk.referencedColumns.push(row.referenced_column);
58
+ tableFks.set(row.constraint_name, fk);
59
+ foreignKeys.set(row.table_name, tableFks);
60
+ }
61
+ const rowCounts = /* @__PURE__ */ new Map();
62
+ for (const row of rowCountRows) {
63
+ rowCounts.set(row.table_name, Number(row.approx_rows));
64
+ }
65
+ const tables = /* @__PURE__ */ new Map();
66
+ for (const row of columnRows) {
67
+ const table = tables.get(row.table_name) ?? {
68
+ name: row.table_name,
69
+ columns: [],
70
+ primaryKey: [],
71
+ foreignKeys: [...foreignKeys.get(row.table_name)?.values() ?? []],
72
+ approxRowCount: rowCounts.get(row.table_name) ?? 0
73
+ };
74
+ const isPrimaryKey = row.column_key === "PRI";
75
+ if (isPrimaryKey) {
76
+ table.primaryKey.push(row.column_name);
77
+ }
78
+ const column = {
79
+ name: row.column_name,
80
+ dataType: row.data_type,
81
+ kind: classifyMysqlType(row.data_type),
82
+ nullable: row.is_nullable === "YES",
83
+ isPrimaryKey,
84
+ hasDefault: row.column_default !== null || row.extra.includes("auto_increment")
85
+ };
86
+ table.columns.push(column);
87
+ tables.set(row.table_name, table);
88
+ }
89
+ return { tables: [...tables.values()] };
90
+ }
91
+
92
+ // src/adapters/mysql.ts
93
+ var MysqlAdapter = class {
94
+ dialect = "mysql";
95
+ database;
96
+ options;
97
+ pool = null;
98
+ sessionConnection = null;
99
+ transactionOpen = false;
100
+ constructor(options) {
101
+ this.options = options;
102
+ this.database = options.database;
103
+ }
104
+ async connect() {
105
+ const mysql = await import("mysql2/promise");
106
+ const config = {
107
+ host: this.options.host,
108
+ port: this.options.port,
109
+ database: this.options.database,
110
+ user: this.options.user,
111
+ password: this.options.password ?? void 0,
112
+ ssl: this.options.ssl ? { rejectUnauthorized: false } : void 0,
113
+ connectionLimit: 3
114
+ };
115
+ try {
116
+ this.pool = mysql.createPool(config);
117
+ this.sessionConnection = await mysql.createConnection(config);
118
+ await this.pool.query("SELECT 1");
119
+ } catch (error) {
120
+ await this.close();
121
+ throw new ConnectionError(
122
+ `Could not connect to mysql://${this.options.user}@${this.options.host}:${this.options.port}/${this.options.database}: ${error.message}`
123
+ );
124
+ }
125
+ }
126
+ async query(sql, params = []) {
127
+ if (!this.pool) {
128
+ throw new ConnectionError("Not connected");
129
+ }
130
+ return this.run(() => this.pool.query(sql, params), sql);
131
+ }
132
+ async sessionQuery(sql, params = []) {
133
+ if (!this.sessionConnection) {
134
+ throw new ConnectionError("Not connected");
135
+ }
136
+ const result = await this.run(() => this.sessionConnection.query(sql, params), sql);
137
+ this.transactionOpen = trackTransactionState(sql, this.transactionOpen);
138
+ return result;
139
+ }
140
+ async run(execute, sql) {
141
+ const started = performance.now();
142
+ let rows;
143
+ let fields;
144
+ try {
145
+ [rows, fields] = await execute();
146
+ } catch (error) {
147
+ throw new ExecutionError(error.message);
148
+ }
149
+ const durationMs = Math.round(performance.now() - started);
150
+ const command = sql.trim().split(/\s+/)[0]?.toUpperCase() ?? "";
151
+ if (Array.isArray(rows)) {
152
+ const fieldNames = Array.isArray(fields) ? fields.map((field) => field.name) : [];
153
+ return {
154
+ columns: fieldNames,
155
+ rows,
156
+ rowCount: rows.length,
157
+ durationMs,
158
+ command
159
+ };
160
+ }
161
+ return {
162
+ columns: [],
163
+ rows: [],
164
+ rowCount: rows?.affectedRows ?? 0,
165
+ durationMs,
166
+ command
167
+ };
168
+ }
169
+ async introspect() {
170
+ const [columns, foreignKeys, rowCounts] = await Promise.all([
171
+ this.query(mysqlColumnsQuery),
172
+ this.query(mysqlForeignKeysQuery),
173
+ this.query(mysqlRowCountsQuery)
174
+ ]);
175
+ return assembleMysqlSchema(
176
+ columns.rows,
177
+ foreignKeys.rows,
178
+ rowCounts.rows
179
+ );
180
+ }
181
+ inTransaction() {
182
+ return this.transactionOpen;
183
+ }
184
+ async close() {
185
+ if (this.sessionConnection) {
186
+ try {
187
+ await this.sessionConnection.end();
188
+ } catch {
189
+ }
190
+ this.sessionConnection = null;
191
+ }
192
+ if (this.pool) {
193
+ try {
194
+ await this.pool.end();
195
+ } catch {
196
+ }
197
+ this.pool = null;
198
+ }
199
+ this.transactionOpen = false;
200
+ }
201
+ };
202
+ export {
203
+ MysqlAdapter
204
+ };
@@ -0,0 +1,67 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ plainTable
4
+ } from "./chunk-7WNCISNV.js";
5
+ import {
6
+ deleteProfile,
7
+ listProfiles,
8
+ saveProfile
9
+ } from "./chunk-3QAR6XBK.js";
10
+ import "./chunk-VFC3HWTF.js";
11
+
12
+ // src/commands/profiles.ts
13
+ import pc from "picocolors";
14
+ function runProfilesList() {
15
+ const profiles = listProfiles();
16
+ if (profiles.length === 0) {
17
+ console.log(pc.dim("No saved profiles. Add one with: midql profiles add <name> ..."));
18
+ return;
19
+ }
20
+ const rows = profiles.map((profile) => [
21
+ profile.name,
22
+ profile.dialect,
23
+ `${profile.user}@${profile.host}:${profile.port}/${profile.database}`,
24
+ profile.env,
25
+ profile.readonly ? "read-only" : "read-write",
26
+ profile.encryptedPassword ? "stored (encrypted)" : profile.passwordEnv ? `env:${profile.passwordEnv}` : "none"
27
+ ]);
28
+ console.log(plainTable(["name", "dialect", "target", "env", "mode", "password"], rows));
29
+ }
30
+ function runProfilesAdd(name, options) {
31
+ if (options.dialect !== "postgres" && options.dialect !== "mysql") {
32
+ console.error(pc.red(`Invalid dialect "${options.dialect}"; use postgres or mysql.`));
33
+ return 1;
34
+ }
35
+ const env = ["dev", "staging", "prod"].includes(options.env) ? options.env : "dev";
36
+ const profile = {
37
+ name,
38
+ dialect: options.dialect,
39
+ host: options.host,
40
+ port: options.port ? Number(options.port) : options.dialect === "postgres" ? 5432 : 3306,
41
+ database: options.database,
42
+ user: options.user,
43
+ encryptedPassword: null,
44
+ passwordEnv: options.passwordEnv ?? null,
45
+ env,
46
+ readonly: options.readonly,
47
+ ssl: options.ssl
48
+ };
49
+ saveProfile(profile, options.password);
50
+ console.log(pc.green(`Profile "${name}" saved.`));
51
+ if (options.password) {
52
+ console.log(
53
+ pc.dim("Password stored encrypted in ~/.midql/config.json (key in ~/.midql/.key).")
54
+ );
55
+ }
56
+ return 0;
57
+ }
58
+ function runProfilesRemove(name) {
59
+ deleteProfile(name);
60
+ console.log(pc.green(`Profile "${name}" removed.`));
61
+ return 0;
62
+ }
63
+ export {
64
+ runProfilesAdd,
65
+ runProfilesList,
66
+ runProfilesRemove
67
+ };