mpx-db 1.1.2 → 1.2.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 +19 -9
- package/README.md +119 -342
- package/package.json +19 -15
- package/src/cli.js +20 -11
- package/src/commands/connections.js +2 -0
- package/src/commands/data.js +4 -2
- package/src/commands/migrate.js +42 -10
- package/src/commands/query.js +10 -5
- package/src/commands/schema.js +25 -5
- package/src/db/base-adapter.js +17 -1
- package/src/db/mysql-adapter.js +6 -1
- package/src/db/postgres-adapter.js +12 -1
- package/src/db/sqlite-adapter.js +5 -5
- package/src/mcp.js +1 -1
- package/src/reporters/pdf.js +286 -0
- package/src/schema.js +39 -2
package/package.json
CHANGED
|
@@ -1,40 +1,45 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mpx-db",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "Database management CLI
|
|
3
|
+
"version": "1.2.0",
|
|
4
|
+
"description": "Database management CLI. Connect, query, migrate, and manage databases. AI-native with JSON output and MCP server.",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"bin": {
|
|
7
7
|
"mpx-db": "bin/mpx-db.js"
|
|
8
8
|
},
|
|
9
9
|
"type": "module",
|
|
10
10
|
"scripts": {
|
|
11
|
-
"test": "node --test test
|
|
11
|
+
"test": "node --test test/*.test.js",
|
|
12
12
|
"test:watch": "node --test --watch test/**/*.test.js",
|
|
13
13
|
"dev": "node bin/mpx-db.js"
|
|
14
14
|
},
|
|
15
|
+
"funding": "https://mesaplex.com/pricing",
|
|
15
16
|
"keywords": [
|
|
16
|
-
"database",
|
|
17
17
|
"cli",
|
|
18
|
+
"devtools",
|
|
19
|
+
"mesaplex",
|
|
20
|
+
"ai-native",
|
|
21
|
+
"mcp",
|
|
22
|
+
"model-context-protocol",
|
|
23
|
+
"automation",
|
|
24
|
+
"json-output",
|
|
25
|
+
"database",
|
|
18
26
|
"migration",
|
|
19
27
|
"postgresql",
|
|
20
28
|
"mysql",
|
|
21
29
|
"sqlite",
|
|
22
30
|
"schema",
|
|
23
|
-
"sql"
|
|
24
|
-
"mcp",
|
|
25
|
-
"ai-native",
|
|
26
|
-
"model-context-protocol",
|
|
27
|
-
"automation",
|
|
28
|
-
"json-output"
|
|
31
|
+
"sql"
|
|
29
32
|
],
|
|
30
33
|
"author": "Mesaplex <support@mesaplex.com>",
|
|
31
|
-
"license": "
|
|
34
|
+
"license": "SEE LICENSE IN LICENSE",
|
|
32
35
|
"repository": {
|
|
33
36
|
"type": "git",
|
|
34
37
|
"url": "git+https://github.com/mesaplexdev/mpx-db.git"
|
|
35
38
|
},
|
|
36
39
|
"homepage": "https://github.com/mesaplexdev/mpx-db#readme",
|
|
37
|
-
"bugs":
|
|
40
|
+
"bugs": {
|
|
41
|
+
"url": "https://github.com/mesaplexdev/mpx-db/issues"
|
|
42
|
+
},
|
|
38
43
|
"engines": {
|
|
39
44
|
"node": ">=18.0.0"
|
|
40
45
|
},
|
|
@@ -44,8 +49,7 @@
|
|
|
44
49
|
"chalk": "^5.3.0",
|
|
45
50
|
"cli-table3": "^0.6.5",
|
|
46
51
|
"commander": "^12.1.0",
|
|
47
|
-
"
|
|
48
|
-
"yaml": "^2.6.1"
|
|
52
|
+
"pdfkit": "^0.17.2"
|
|
49
53
|
},
|
|
50
54
|
"peerDependencies": {
|
|
51
55
|
"mysql2": "^3.11.5",
|
|
@@ -64,6 +68,6 @@
|
|
|
64
68
|
"bin/",
|
|
65
69
|
"README.md",
|
|
66
70
|
"LICENSE",
|
|
67
|
-
"
|
|
71
|
+
"CHANGELOG.md"
|
|
68
72
|
]
|
|
69
73
|
}
|
package/src/cli.js
CHANGED
|
@@ -31,15 +31,24 @@ program
|
|
|
31
31
|
.version(pkg.version)
|
|
32
32
|
.option('--json', 'Output as JSON (machine-readable)')
|
|
33
33
|
.option('-q, --quiet', 'Suppress non-essential output')
|
|
34
|
+
.option('--no-color', 'Disable colored output')
|
|
34
35
|
.option('--schema', 'Output JSON schema describing all commands and flags')
|
|
35
|
-
.
|
|
36
|
+
.option('--pdf <file>', 'Export results as a PDF report');
|
|
37
|
+
|
|
38
|
+
// Error handling — must be set BEFORE .command() so subcommands inherit exitOverride
|
|
39
|
+
program.exitOverride();
|
|
40
|
+
program.configureOutput({
|
|
41
|
+
writeErr: () => {} // Suppress Commander's own error output; we handle it in the catch below
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
program.hook('preAction', (thisCommand) => {
|
|
36
45
|
// Merge parent options with command options
|
|
37
46
|
const parentOpts = thisCommand.parent?.opts() || {};
|
|
38
47
|
const opts = thisCommand.opts();
|
|
39
48
|
globalOptions = { ...parentOpts, ...opts };
|
|
40
49
|
|
|
41
|
-
// Disable chalk if JSON mode
|
|
42
|
-
if (globalOptions.json) {
|
|
50
|
+
// Disable chalk if JSON mode or --no-color
|
|
51
|
+
if (globalOptions.json || globalOptions.color === false) {
|
|
43
52
|
chalk.level = 0;
|
|
44
53
|
}
|
|
45
54
|
});
|
|
@@ -74,7 +83,8 @@ program
|
|
|
74
83
|
.description('Execute a SQL query')
|
|
75
84
|
.argument('<target>', 'Connection name or URL')
|
|
76
85
|
.argument('<sql>', 'SQL query to execute')
|
|
77
|
-
.
|
|
86
|
+
.option('--pdf <file>', 'Export query results as PDF report')
|
|
87
|
+
.action((target, sql, options) => handleQuery(target, sql, { ...globalOptions, ...options }));
|
|
78
88
|
|
|
79
89
|
// Info command
|
|
80
90
|
program
|
|
@@ -107,7 +117,8 @@ schema
|
|
|
107
117
|
.command('dump')
|
|
108
118
|
.description('Dump database schema as SQL')
|
|
109
119
|
.argument('<target>', 'Connection name or URL')
|
|
110
|
-
.
|
|
120
|
+
.option('--pdf <file>', 'Export schema as PDF report')
|
|
121
|
+
.action((target, options) => dumpSchema(target, { ...globalOptions, ...options }));
|
|
111
122
|
|
|
112
123
|
// Migration commands
|
|
113
124
|
const migrate = program
|
|
@@ -218,7 +229,7 @@ program
|
|
|
218
229
|
if (jsonMode) {
|
|
219
230
|
console.log(JSON.stringify({ error: err.message, code: 'ERR_UPDATE' }, null, 2));
|
|
220
231
|
} else {
|
|
221
|
-
console.error(chalk.red
|
|
232
|
+
console.error(chalk.red('Error:'), err.message);
|
|
222
233
|
console.error('');
|
|
223
234
|
}
|
|
224
235
|
process.exit(1);
|
|
@@ -245,9 +256,6 @@ if (process.argv.includes('--schema')) {
|
|
|
245
256
|
process.exit(0);
|
|
246
257
|
}
|
|
247
258
|
|
|
248
|
-
// Error handling
|
|
249
|
-
program.exitOverride();
|
|
250
|
-
|
|
251
259
|
try {
|
|
252
260
|
await program.parseAsync(process.argv);
|
|
253
261
|
} catch (err) {
|
|
@@ -256,7 +264,8 @@ try {
|
|
|
256
264
|
process.exit(0);
|
|
257
265
|
}
|
|
258
266
|
if (err.code !== 'commander.help' && err.code !== 'commander.helpDisplayed') {
|
|
259
|
-
|
|
260
|
-
|
|
267
|
+
const msg = err.message.startsWith('error:') ? `Error: ${err.message.slice(7)}` : `Error: ${err.message}`;
|
|
268
|
+
console.error(chalk.red(msg));
|
|
269
|
+
process.exit(2);
|
|
261
270
|
}
|
|
262
271
|
}
|
|
@@ -120,6 +120,7 @@ export async function removeConnection(name, options = {}) {
|
|
|
120
120
|
name,
|
|
121
121
|
message: deleted ? 'Connection deleted' : 'Connection not found'
|
|
122
122
|
}, null, 2));
|
|
123
|
+
if (!deleted) process.exit(1);
|
|
123
124
|
return;
|
|
124
125
|
}
|
|
125
126
|
|
|
@@ -128,5 +129,6 @@ export async function removeConnection(name, options = {}) {
|
|
|
128
129
|
console.log(chalk.green(`✓ Deleted connection "${name}"`));
|
|
129
130
|
} else {
|
|
130
131
|
console.log(chalk.yellow(`Connection "${name}" not found`));
|
|
132
|
+
process.exit(1);
|
|
131
133
|
}
|
|
132
134
|
}
|
package/src/commands/data.js
CHANGED
|
@@ -14,10 +14,12 @@ export async function exportData(target, tableName, options = {}) {
|
|
|
14
14
|
db = await createConnection(connectionString);
|
|
15
15
|
|
|
16
16
|
// Query all data
|
|
17
|
-
const rows = await db.query(`SELECT * FROM ${tableName}`);
|
|
17
|
+
const rows = await db.query(`SELECT * FROM ${db.quoteIdentifier(tableName)}`);
|
|
18
18
|
|
|
19
19
|
if (rows.length === 0) {
|
|
20
|
-
if (
|
|
20
|
+
if (options.json) {
|
|
21
|
+
console.log(JSON.stringify({ success: true, rows: [], rowCount: 0 }, null, 2));
|
|
22
|
+
} else if (!options.quiet) {
|
|
21
23
|
console.log(chalk.yellow('No data to export'));
|
|
22
24
|
}
|
|
23
25
|
return;
|
package/src/commands/migrate.js
CHANGED
|
@@ -7,6 +7,44 @@ import { resolveConnection } from './query.js';
|
|
|
7
7
|
|
|
8
8
|
const MIGRATIONS_DIR = './migrations';
|
|
9
9
|
|
|
10
|
+
/**
|
|
11
|
+
* Split SQL into statements on semicolons, respecting quoted strings.
|
|
12
|
+
*/
|
|
13
|
+
function splitStatements(sql) {
|
|
14
|
+
const statements = [];
|
|
15
|
+
let current = '';
|
|
16
|
+
let inSingle = false;
|
|
17
|
+
let inDouble = false;
|
|
18
|
+
|
|
19
|
+
for (let i = 0; i < sql.length; i++) {
|
|
20
|
+
const ch = sql[i];
|
|
21
|
+
const prev = i > 0 ? sql[i - 1] : '';
|
|
22
|
+
|
|
23
|
+
if (ch === "'" && !inDouble && prev !== '\\') {
|
|
24
|
+
inSingle = !inSingle;
|
|
25
|
+
} else if (ch === '"' && !inSingle && prev !== '\\') {
|
|
26
|
+
inDouble = !inDouble;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (ch === ';' && !inSingle && !inDouble) {
|
|
30
|
+
const trimmed = current.trim();
|
|
31
|
+
if (trimmed.length > 0) {
|
|
32
|
+
statements.push(trimmed);
|
|
33
|
+
}
|
|
34
|
+
current = '';
|
|
35
|
+
} else {
|
|
36
|
+
current += ch;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const trimmed = current.trim();
|
|
41
|
+
if (trimmed.length > 0) {
|
|
42
|
+
statements.push(trimmed);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return statements;
|
|
46
|
+
}
|
|
47
|
+
|
|
10
48
|
/**
|
|
11
49
|
* Initialize migrations directory
|
|
12
50
|
*/
|
|
@@ -287,17 +325,14 @@ export async function runMigrations(target, options = {}) {
|
|
|
287
325
|
console.log(chalk.gray(`→ ${name}`));
|
|
288
326
|
}
|
|
289
327
|
|
|
290
|
-
// Execute migration (split by semicolon
|
|
328
|
+
// Execute migration (split by semicolon, respecting quoted strings)
|
|
291
329
|
// Remove comment lines first, then split
|
|
292
330
|
const cleanedSQL = migration.up
|
|
293
331
|
.split('\n')
|
|
294
332
|
.filter(line => !line.trim().startsWith('--'))
|
|
295
333
|
.join('\n');
|
|
296
334
|
|
|
297
|
-
const statements = cleanedSQL
|
|
298
|
-
.split(';')
|
|
299
|
-
.map(s => s.trim())
|
|
300
|
-
.filter(s => s.length > 0);
|
|
335
|
+
const statements = splitStatements(cleanedSQL);
|
|
301
336
|
|
|
302
337
|
for (const statement of statements) {
|
|
303
338
|
await db.execute(statement);
|
|
@@ -385,17 +420,14 @@ export async function rollbackMigration(target, options = {}) {
|
|
|
385
420
|
console.log(chalk.cyan(`Rolling back: ${last.name}`));
|
|
386
421
|
}
|
|
387
422
|
|
|
388
|
-
// Execute rollback (split by semicolon
|
|
423
|
+
// Execute rollback (split by semicolon, respecting quoted strings)
|
|
389
424
|
// Remove comment lines first, then split
|
|
390
425
|
const cleanedSQL = migration.down
|
|
391
426
|
.split('\n')
|
|
392
427
|
.filter(line => !line.trim().startsWith('--'))
|
|
393
428
|
.join('\n');
|
|
394
429
|
|
|
395
|
-
const statements = cleanedSQL
|
|
396
|
-
.split(';')
|
|
397
|
-
.map(s => s.trim())
|
|
398
|
-
.filter(s => s.length > 0);
|
|
430
|
+
const statements = splitStatements(cleanedSQL);
|
|
399
431
|
|
|
400
432
|
for (const statement of statements) {
|
|
401
433
|
await db.execute(statement);
|
package/src/commands/query.js
CHANGED
|
@@ -2,6 +2,7 @@ import chalk from 'chalk';
|
|
|
2
2
|
import Table from 'cli-table3';
|
|
3
3
|
import { getConnection } from '../utils/config.js';
|
|
4
4
|
import { createConnection } from '../db/connection.js';
|
|
5
|
+
import { generateQueryPDF } from '../reporters/pdf.js';
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* Execute a query or statement
|
|
@@ -27,8 +28,15 @@ export async function handleQuery(target, sql, options = {}) {
|
|
|
27
28
|
const rows = await db.query(sql);
|
|
28
29
|
const duration = Date.now() - startTime;
|
|
29
30
|
|
|
31
|
+
// PDF output
|
|
32
|
+
if (options.pdf) {
|
|
33
|
+
await generateQueryPDF(rows, { sql, duration }, options.pdf);
|
|
34
|
+
if (!options.quiet) {
|
|
35
|
+
console.log(chalk.green(`✓ Query report saved to ${options.pdf} (${rows.length} rows)`));
|
|
36
|
+
}
|
|
37
|
+
}
|
|
30
38
|
// JSON output
|
|
31
|
-
if (options.json) {
|
|
39
|
+
else if (options.json) {
|
|
32
40
|
console.log(JSON.stringify({
|
|
33
41
|
success: true,
|
|
34
42
|
type: 'query',
|
|
@@ -36,15 +44,12 @@ export async function handleQuery(target, sql, options = {}) {
|
|
|
36
44
|
rowCount: rows.length,
|
|
37
45
|
duration
|
|
38
46
|
}, null, 2));
|
|
39
|
-
} else {
|
|
47
|
+
} else if (!options.quiet) {
|
|
40
48
|
// Display results
|
|
41
49
|
if (rows.length === 0) {
|
|
42
50
|
console.log(chalk.yellow('No rows returned'));
|
|
43
51
|
} else {
|
|
44
52
|
displayTable(rows);
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
if (!options.quiet) {
|
|
48
53
|
console.log(chalk.gray(`\n${rows.length} row(s) in ${duration}ms`));
|
|
49
54
|
}
|
|
50
55
|
}
|
package/src/commands/schema.js
CHANGED
|
@@ -2,6 +2,7 @@ import chalk from 'chalk';
|
|
|
2
2
|
import Table from 'cli-table3';
|
|
3
3
|
import { createConnection } from '../db/connection.js';
|
|
4
4
|
import { resolveConnection } from './query.js';
|
|
5
|
+
import { generateSchemaPDF } from '../reporters/pdf.js';
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* Show database info
|
|
@@ -135,9 +136,9 @@ export async function describeTable(target, tableName, options = {}) {
|
|
|
135
136
|
if (options.json) {
|
|
136
137
|
console.log(JSON.stringify({ error: `Table "${tableName}" not found` }, null, 2));
|
|
137
138
|
} else {
|
|
138
|
-
console.
|
|
139
|
+
console.error(chalk.yellow(`Table "${tableName}" not found or has no columns`));
|
|
139
140
|
}
|
|
140
|
-
|
|
141
|
+
process.exit(1);
|
|
141
142
|
}
|
|
142
143
|
|
|
143
144
|
// JSON output
|
|
@@ -216,19 +217,38 @@ export async function dumpSchema(target, options = {}) {
|
|
|
216
217
|
sqlOutput += `-- Table: ${table.name}\n`;
|
|
217
218
|
sqlOutput += `-- Rows: ${table.rows}\n`;
|
|
218
219
|
|
|
219
|
-
|
|
220
|
+
const pkCols = schema.filter(c => c.primaryKey).map(c => c.name);
|
|
220
221
|
const cols = schema.map(c => {
|
|
221
222
|
let def = ` ${c.name} ${c.type}`;
|
|
222
|
-
if (
|
|
223
|
+
if (c.primaryKey && pkCols.length === 1) def += ' PRIMARY KEY';
|
|
224
|
+
if (!c.nullable && !c.primaryKey) def += ' NOT NULL';
|
|
223
225
|
if (c.default) def += ` DEFAULT ${c.default}`;
|
|
224
226
|
return def;
|
|
225
227
|
});
|
|
226
228
|
|
|
227
|
-
sqlOutput += `CREATE TABLE ${table.name} (\n`;
|
|
229
|
+
sqlOutput += `CREATE TABLE "${table.name}" (\n`;
|
|
228
230
|
sqlOutput += cols.join(',\n');
|
|
231
|
+
if (pkCols.length > 1) {
|
|
232
|
+
sqlOutput += `,\n PRIMARY KEY (${pkCols.join(', ')})`;
|
|
233
|
+
}
|
|
229
234
|
sqlOutput += '\n);\n\n';
|
|
230
235
|
}
|
|
231
236
|
|
|
237
|
+
// PDF output
|
|
238
|
+
if (options.pdf) {
|
|
239
|
+
const tableSchemas = [];
|
|
240
|
+
for (const table of tables) {
|
|
241
|
+
if (table.type !== 'table') continue;
|
|
242
|
+
const cols = await db.getTableSchema(table.name);
|
|
243
|
+
tableSchemas.push({ name: table.name, columns: cols });
|
|
244
|
+
}
|
|
245
|
+
await generateSchemaPDF(info, tables, tableSchemas, options.pdf);
|
|
246
|
+
if (!options.quiet) {
|
|
247
|
+
console.log(chalk.green(`✓ Schema report saved to ${options.pdf}`));
|
|
248
|
+
}
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
|
|
232
252
|
// JSON output
|
|
233
253
|
if (options.json) {
|
|
234
254
|
console.log(JSON.stringify({ sql: sqlOutput }, null, 2));
|
package/src/db/base-adapter.js
CHANGED
|
@@ -8,6 +8,22 @@ export class BaseAdapter {
|
|
|
8
8
|
this.connection = null;
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
+
/**
|
|
12
|
+
* Validate and quote a table/identifier name to prevent SQL injection.
|
|
13
|
+
* Rejects names with dangerous characters. Override quoteChar in subclasses.
|
|
14
|
+
*/
|
|
15
|
+
quoteIdentifier(name) {
|
|
16
|
+
if (!name || typeof name !== 'string') {
|
|
17
|
+
throw new Error('Invalid identifier: must be a non-empty string');
|
|
18
|
+
}
|
|
19
|
+
// Allow alphanumeric, underscores, dots (schema.table), hyphens
|
|
20
|
+
if (!/^[a-zA-Z_][a-zA-Z0-9_.\\-]*$/.test(name)) {
|
|
21
|
+
throw new Error(`Invalid identifier: "${name}" contains disallowed characters`);
|
|
22
|
+
}
|
|
23
|
+
const q = this._identifierQuote || '"';
|
|
24
|
+
return `${q}${name.replace(new RegExp(`\\${q}`, 'g'), q + q)}${q}`;
|
|
25
|
+
}
|
|
26
|
+
|
|
11
27
|
/**
|
|
12
28
|
* Connect to database
|
|
13
29
|
*/
|
|
@@ -63,7 +79,7 @@ export class BaseAdapter {
|
|
|
63
79
|
* Get table row count
|
|
64
80
|
*/
|
|
65
81
|
async getRowCount(tableName) {
|
|
66
|
-
const result = await this.query(`SELECT COUNT(*) as count FROM ${tableName}`);
|
|
82
|
+
const result = await this.query(`SELECT COUNT(*) as count FROM ${this.quoteIdentifier(tableName)}`);
|
|
67
83
|
return result[0].count;
|
|
68
84
|
}
|
|
69
85
|
|
package/src/db/mysql-adapter.js
CHANGED
|
@@ -4,6 +4,11 @@ import { BaseAdapter } from './base-adapter.js';
|
|
|
4
4
|
* MySQL adapter using mysql2
|
|
5
5
|
*/
|
|
6
6
|
export class MySQLAdapter extends BaseAdapter {
|
|
7
|
+
constructor(connectionString) {
|
|
8
|
+
super(connectionString);
|
|
9
|
+
this._identifierQuote = '`';
|
|
10
|
+
}
|
|
11
|
+
|
|
7
12
|
async connect() {
|
|
8
13
|
try {
|
|
9
14
|
const mysql = await import('mysql2/promise');
|
|
@@ -62,7 +67,7 @@ export class MySQLAdapter extends BaseAdapter {
|
|
|
62
67
|
const tables = [];
|
|
63
68
|
for (const row of rows) {
|
|
64
69
|
const countResult = await this.query(
|
|
65
|
-
`SELECT COUNT(*) as count FROM
|
|
70
|
+
`SELECT COUNT(*) as count FROM ${this.quoteIdentifier(row.name)}`
|
|
66
71
|
);
|
|
67
72
|
tables.push({
|
|
68
73
|
name: row.name,
|
|
@@ -64,7 +64,7 @@ export class PostgresAdapter extends BaseAdapter {
|
|
|
64
64
|
const tables = [];
|
|
65
65
|
for (const row of rows) {
|
|
66
66
|
const countResult = await this.query(
|
|
67
|
-
`SELECT COUNT(*) as count FROM ${row.name}`
|
|
67
|
+
`SELECT COUNT(*) as count FROM ${this.quoteIdentifier(row.name)}`
|
|
68
68
|
);
|
|
69
69
|
tables.push({
|
|
70
70
|
name: row.name,
|
|
@@ -127,6 +127,17 @@ export class PostgresAdapter extends BaseAdapter {
|
|
|
127
127
|
};
|
|
128
128
|
}
|
|
129
129
|
|
|
130
|
+
async recordMigration(name) {
|
|
131
|
+
await this.execute(
|
|
132
|
+
'INSERT INTO mpx_migrations (name, applied_at) VALUES ($1, $2)',
|
|
133
|
+
[name, new Date().toISOString()]
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
async removeMigration(name) {
|
|
138
|
+
await this.execute('DELETE FROM mpx_migrations WHERE name = $1', [name]);
|
|
139
|
+
}
|
|
140
|
+
|
|
130
141
|
async ensureMigrationsTable() {
|
|
131
142
|
await this.execute(`
|
|
132
143
|
CREATE TABLE IF NOT EXISTS mpx_migrations (
|
package/src/db/sqlite-adapter.js
CHANGED
|
@@ -10,9 +10,9 @@ export class SQLiteAdapter extends BaseAdapter {
|
|
|
10
10
|
super(connectionString);
|
|
11
11
|
|
|
12
12
|
// Extract file path from sqlite:// or sqlite3://
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
13
|
+
// sqlite:///tmp/foo.db → /tmp/foo.db (absolute)
|
|
14
|
+
// sqlite://mydb.db → mydb.db (relative)
|
|
15
|
+
this.dbPath = connectionString.replace(/^sqlite3?:\/\//, '');
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
async connect() {
|
|
@@ -76,7 +76,7 @@ export class SQLiteAdapter extends BaseAdapter {
|
|
|
76
76
|
|
|
77
77
|
const tables = [];
|
|
78
78
|
for (const row of rows) {
|
|
79
|
-
const countResult = await this.query(`SELECT COUNT(*) as count FROM ${row.name}`);
|
|
79
|
+
const countResult = await this.query(`SELECT COUNT(*) as count FROM ${this.quoteIdentifier(row.name)}`);
|
|
80
80
|
tables.push({
|
|
81
81
|
name: row.name,
|
|
82
82
|
type: row.type,
|
|
@@ -88,7 +88,7 @@ export class SQLiteAdapter extends BaseAdapter {
|
|
|
88
88
|
}
|
|
89
89
|
|
|
90
90
|
async getTableSchema(tableName) {
|
|
91
|
-
const rows = await this.query(`PRAGMA table_info(${tableName})`);
|
|
91
|
+
const rows = await this.query(`PRAGMA table_info(${this.quoteIdentifier(tableName)})`);
|
|
92
92
|
|
|
93
93
|
return rows.map(row => ({
|
|
94
94
|
name: row.name,
|
package/src/mcp.js
CHANGED
|
@@ -261,7 +261,7 @@ export async function startMCPServer() {
|
|
|
261
261
|
const db = await createConnection(connectionString);
|
|
262
262
|
|
|
263
263
|
try {
|
|
264
|
-
const rows = await db.query(`SELECT * FROM ${args.table}`);
|
|
264
|
+
const rows = await db.query(`SELECT * FROM ${db.quoteIdentifier(args.table)}`);
|
|
265
265
|
await db.disconnect();
|
|
266
266
|
|
|
267
267
|
return {
|