mpx-db 1.1.3 โ 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/README.md +119 -342
- package/package.json +18 -12
- package/src/cli.js +20 -11
- package/src/commands/query.js +9 -1
- package/src/commands/schema.js +24 -4
- package/src/reporters/pdf.js +286 -0
- package/src/schema.js +39 -2
package/README.md
CHANGED
|
@@ -1,293 +1,167 @@
|
|
|
1
|
-
# mpx-db
|
|
1
|
+
# mpx-db ๐๏ธ
|
|
2
2
|
|
|
3
|
-
**Database management CLI
|
|
3
|
+
**Database management CLI โ connect, query, migrate, and manage databases from the terminal.**
|
|
4
4
|
|
|
5
|
-
Stop juggling multiple database tools.
|
|
5
|
+
Stop juggling multiple database tools. One clean interface for SQLite, PostgreSQL, and MySQL.
|
|
6
|
+
|
|
7
|
+
Part of the [Mesaplex](https://mesaplex.com) developer toolchain.
|
|
8
|
+
|
|
9
|
+
[](https://www.npmjs.com/package/mpx-db)
|
|
10
|
+
[](LICENSE)
|
|
11
|
+
[](https://nodejs.org)
|
|
6
12
|
|
|
7
13
|
## Features
|
|
8
14
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
15
|
+
- **Multi-database support** โ SQLite, PostgreSQL, MySQL
|
|
16
|
+
- **Beautiful output** โ Colored tables, not raw dumps
|
|
17
|
+
- **Connection management** โ Save connections, no more copy-pasting URLs
|
|
18
|
+
- **Migration system** โ Git-friendly SQL migration files
|
|
19
|
+
- **Schema operations** โ Dump, describe, visualize database structure
|
|
20
|
+
- **Data export** โ Export to JSON/CSV with one command
|
|
21
|
+
- **Secure** โ Encrypted credential storage (AES-256-GCM)
|
|
22
|
+
- **MCP server** โ Integrates with any MCP-compatible AI agent
|
|
23
|
+
- **Self-documenting** โ `--schema` returns machine-readable tool description
|
|
17
24
|
|
|
18
25
|
## Installation
|
|
19
26
|
|
|
20
27
|
```bash
|
|
21
28
|
npm install -g mpx-db
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Install database drivers you need (optional peer dependencies):
|
|
22
32
|
|
|
23
|
-
|
|
33
|
+
```bash
|
|
24
34
|
npm install -g better-sqlite3 # For SQLite
|
|
25
35
|
npm install -g pg # For PostgreSQL
|
|
26
36
|
npm install -g mysql2 # For MySQL
|
|
27
37
|
```
|
|
28
38
|
|
|
29
|
-
|
|
39
|
+
Or run directly with npx:
|
|
30
40
|
|
|
31
41
|
```bash
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
# Save a connection for reuse
|
|
36
|
-
mpx-db connect --save dev sqlite://./dev.db
|
|
37
|
-
mpx-db connect --save prod postgres://user:pass@localhost:5432/mydb
|
|
42
|
+
npx mpx-db --help
|
|
43
|
+
```
|
|
38
44
|
|
|
39
|
-
|
|
40
|
-
mpx-db connections list
|
|
45
|
+
**Requirements:** Node.js 18+ ยท macOS, Linux, Windows
|
|
41
46
|
|
|
42
|
-
|
|
43
|
-
mpx-db query dev "SELECT * FROM users LIMIT 10"
|
|
47
|
+
## Quick Start
|
|
44
48
|
|
|
45
|
-
|
|
46
|
-
|
|
49
|
+
```bash
|
|
50
|
+
# Connect and save
|
|
51
|
+
mpx-db connect --save dev sqlite://./dev.db
|
|
47
52
|
|
|
48
|
-
# List
|
|
53
|
+
# List tables
|
|
49
54
|
mpx-db tables dev
|
|
50
55
|
|
|
56
|
+
# Run a query
|
|
57
|
+
mpx-db query dev "SELECT * FROM users LIMIT 10"
|
|
58
|
+
|
|
51
59
|
# Describe a table
|
|
52
60
|
mpx-db describe dev users
|
|
53
|
-
```
|
|
54
|
-
|
|
55
|
-
## Connection Strings
|
|
56
|
-
|
|
57
|
-
```bash
|
|
58
|
-
# SQLite (file-based)
|
|
59
|
-
sqlite://./database.db
|
|
60
|
-
sqlite:///absolute/path/to/db.sqlite
|
|
61
|
-
|
|
62
|
-
# PostgreSQL
|
|
63
|
-
postgres://user:password@localhost:5432/database
|
|
64
|
-
postgresql://user:password@host:5432/db
|
|
65
61
|
|
|
66
|
-
#
|
|
67
|
-
|
|
62
|
+
# Export data
|
|
63
|
+
mpx-db export dev users --format json
|
|
68
64
|
```
|
|
69
65
|
|
|
70
|
-
##
|
|
66
|
+
## Usage
|
|
71
67
|
|
|
72
68
|
### Connection Management
|
|
73
69
|
|
|
74
70
|
```bash
|
|
75
|
-
|
|
76
|
-
mpx-db
|
|
71
|
+
mpx-db connect --save <name> <url> # Save a connection
|
|
72
|
+
mpx-db connections list # List saved connections
|
|
73
|
+
mpx-db connections remove <name> # Remove a connection
|
|
74
|
+
```
|
|
77
75
|
|
|
78
|
-
|
|
79
|
-
mpx-db connections list
|
|
76
|
+
Connection string formats:
|
|
80
77
|
|
|
81
|
-
|
|
82
|
-
|
|
78
|
+
```
|
|
79
|
+
sqlite://./database.db
|
|
80
|
+
postgres://user:password@localhost:5432/database
|
|
81
|
+
mysql://user:password@localhost:3306/database
|
|
83
82
|
```
|
|
84
83
|
|
|
85
84
|
### Querying
|
|
86
85
|
|
|
87
86
|
```bash
|
|
88
|
-
# Run a query
|
|
89
87
|
mpx-db query <connection> "SELECT * FROM users WHERE active = 1"
|
|
90
|
-
|
|
91
|
-
# Query with saved connection
|
|
92
|
-
mpx-db query dev "SELECT COUNT(*) FROM orders"
|
|
93
88
|
```
|
|
94
89
|
|
|
95
90
|
### Schema Operations
|
|
96
91
|
|
|
97
92
|
```bash
|
|
98
|
-
#
|
|
99
|
-
mpx-db
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
mpx-db tables <connection>
|
|
103
|
-
|
|
104
|
-
# Describe table structure
|
|
105
|
-
mpx-db describe <connection> <table>
|
|
106
|
-
|
|
107
|
-
# Dump entire schema as SQL
|
|
108
|
-
mpx-db schema dump <connection>
|
|
93
|
+
mpx-db info <connection> # Database information
|
|
94
|
+
mpx-db tables <connection> # List tables with row counts
|
|
95
|
+
mpx-db describe <connection> <table> # Table structure
|
|
96
|
+
mpx-db schema dump <connection> # Dump schema as SQL
|
|
109
97
|
```
|
|
110
98
|
|
|
111
99
|
### Migrations
|
|
112
100
|
|
|
113
101
|
```bash
|
|
114
|
-
# Initialize migrations directory
|
|
115
|
-
mpx-db migrate
|
|
116
|
-
|
|
117
|
-
#
|
|
118
|
-
mpx-db migrate
|
|
119
|
-
|
|
120
|
-
# Show migration status
|
|
121
|
-
mpx-db migrate status <connection>
|
|
122
|
-
|
|
123
|
-
# Run pending migrations
|
|
124
|
-
mpx-db migrate up <connection>
|
|
125
|
-
|
|
126
|
-
# Rollback last migration
|
|
127
|
-
mpx-db migrate down <connection>
|
|
102
|
+
mpx-db migrate init # Initialize migrations directory
|
|
103
|
+
mpx-db migrate create <name> # Create a new migration
|
|
104
|
+
mpx-db migrate status <connection> # Show migration status
|
|
105
|
+
mpx-db migrate up <connection> # Run pending migrations
|
|
106
|
+
mpx-db migrate down <connection> # Rollback last migration
|
|
128
107
|
```
|
|
129
108
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
Migrations are SQL files in `./migrations/` directory:
|
|
109
|
+
Migration file format (`./migrations/`):
|
|
133
110
|
|
|
134
111
|
```sql
|
|
135
|
-
-- Migration: add_users_table
|
|
136
|
-
-- Created: 2026-02-15T10:30:00.000Z
|
|
137
|
-
|
|
138
112
|
-- Up migration
|
|
139
113
|
CREATE TABLE users (
|
|
140
114
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
141
115
|
email TEXT UNIQUE NOT NULL,
|
|
142
|
-
name TEXT NOT NULL
|
|
143
|
-
created_at TEXT DEFAULT CURRENT_TIMESTAMP
|
|
116
|
+
name TEXT NOT NULL
|
|
144
117
|
);
|
|
145
118
|
|
|
146
|
-
CREATE INDEX idx_users_email ON users(email);
|
|
147
|
-
|
|
148
|
-
-- Down migration (rollback)
|
|
149
119
|
-- DOWN
|
|
150
|
-
DROP INDEX idx_users_email;
|
|
151
120
|
DROP TABLE users;
|
|
152
121
|
```
|
|
153
122
|
|
|
154
|
-
### Data
|
|
123
|
+
### Data Export
|
|
155
124
|
|
|
156
125
|
```bash
|
|
157
|
-
|
|
158
|
-
mpx-db export
|
|
159
|
-
|
|
160
|
-
# Export to CSV
|
|
161
|
-
mpx-db export dev users --format csv --output data.csv
|
|
162
|
-
```
|
|
163
|
-
|
|
164
|
-
## Examples
|
|
165
|
-
|
|
166
|
-
### Setting Up a New Project
|
|
167
|
-
|
|
168
|
-
```bash
|
|
169
|
-
# Initialize migrations
|
|
170
|
-
mpx-db migrate init
|
|
171
|
-
|
|
172
|
-
# Create your first migration
|
|
173
|
-
mpx-db migrate create create_initial_schema
|
|
174
|
-
|
|
175
|
-
# Edit migrations/YYYYMMDD_HHMMSS_create_initial_schema.sql
|
|
176
|
-
# Add your CREATE TABLE statements
|
|
177
|
-
|
|
178
|
-
# Connect to database
|
|
179
|
-
mpx-db connect --save dev sqlite://./dev.db
|
|
180
|
-
|
|
181
|
-
# Run migrations
|
|
182
|
-
mpx-db migrate up dev
|
|
183
|
-
|
|
184
|
-
# Verify
|
|
185
|
-
mpx-db tables dev
|
|
186
|
-
mpx-db info dev
|
|
187
|
-
```
|
|
188
|
-
|
|
189
|
-
### Managing Multiple Environments
|
|
190
|
-
|
|
191
|
-
```bash
|
|
192
|
-
# Save connections for each environment
|
|
193
|
-
mpx-db connect --save dev sqlite://./dev.db
|
|
194
|
-
mpx-db connect --save staging postgres://user:pass@staging-host/mydb
|
|
195
|
-
mpx-db connect --save prod postgres://user:pass@prod-host/mydb
|
|
196
|
-
|
|
197
|
-
# Run migrations on each
|
|
198
|
-
mpx-db migrate up dev
|
|
199
|
-
mpx-db migrate up staging
|
|
200
|
-
mpx-db migrate up prod
|
|
201
|
-
|
|
202
|
-
# Compare schemas (visual inspection)
|
|
203
|
-
mpx-db schema dump dev > dev-schema.sql
|
|
204
|
-
mpx-db schema dump prod > prod-schema.sql
|
|
205
|
-
diff dev-schema.sql prod-schema.sql
|
|
206
|
-
```
|
|
207
|
-
|
|
208
|
-
### Daily Workflow
|
|
209
|
-
|
|
210
|
-
```bash
|
|
211
|
-
# Check database status
|
|
212
|
-
mpx-db info dev
|
|
213
|
-
mpx-db tables dev
|
|
214
|
-
|
|
215
|
-
# Run a quick query
|
|
216
|
-
mpx-db query dev "SELECT COUNT(*) FROM orders WHERE created_at > date('now', '-1 day')"
|
|
217
|
-
|
|
218
|
-
# Describe a table before modifying it
|
|
219
|
-
mpx-db describe dev orders
|
|
220
|
-
|
|
221
|
-
# Create a migration for changes
|
|
222
|
-
mpx-db migrate create add_order_status_field
|
|
223
|
-
|
|
224
|
-
# Export data for backup/analysis
|
|
225
|
-
mpx-db export dev orders --format csv --output orders-backup.csv
|
|
126
|
+
mpx-db export <connection> <table> --format json
|
|
127
|
+
mpx-db export <connection> <table> --format csv --output data.csv
|
|
226
128
|
```
|
|
227
129
|
|
|
228
130
|
## AI Agent Usage
|
|
229
131
|
|
|
230
|
-
|
|
132
|
+
mpx-db is designed to be used by AI agents as well as humans.
|
|
231
133
|
|
|
232
134
|
### JSON Output
|
|
233
135
|
|
|
234
|
-
Add `--json` to any command for structured output:
|
|
136
|
+
Add `--json` to any command for structured, machine-readable output:
|
|
235
137
|
|
|
236
138
|
```bash
|
|
237
|
-
# Query with JSON output
|
|
238
139
|
mpx-db query dev "SELECT * FROM users LIMIT 3" --json
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
```json
|
|
239
143
|
{
|
|
240
144
|
"success": true,
|
|
241
145
|
"type": "query",
|
|
242
146
|
"rows": [
|
|
243
|
-
{ "id": 1, "name": "Alice", "email": "alice@example.com" }
|
|
244
|
-
{ "id": 2, "name": "Bob", "email": "bob@example.com" },
|
|
245
|
-
{ "id": 3, "name": "Charlie", "email": "charlie@example.com" }
|
|
147
|
+
{ "id": 1, "name": "Alice", "email": "alice@example.com" }
|
|
246
148
|
],
|
|
247
|
-
"rowCount":
|
|
149
|
+
"rowCount": 1,
|
|
248
150
|
"duration": 12
|
|
249
151
|
}
|
|
250
|
-
|
|
251
|
-
# List tables with JSON
|
|
252
|
-
mpx-db tables dev --json
|
|
253
|
-
{
|
|
254
|
-
"tables": [
|
|
255
|
-
{ "name": "users", "type": "table", "rowCount": 150 },
|
|
256
|
-
{ "name": "orders", "type": "table", "rowCount": 892 }
|
|
257
|
-
]
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
# Migration status
|
|
261
|
-
mpx-db migrate status dev --json
|
|
262
|
-
{
|
|
263
|
-
"migrations": [
|
|
264
|
-
{ "name": "20260215_100000_create_users", "status": "applied", "appliedAt": "2026-02-15T10:05:00.000Z" },
|
|
265
|
-
{ "name": "20260216_120000_add_orders", "status": "pending", "appliedAt": null }
|
|
266
|
-
]
|
|
267
|
-
}
|
|
268
152
|
```
|
|
269
153
|
|
|
270
154
|
### Schema Discovery
|
|
271
155
|
|
|
272
|
-
Get the full command schema for AI agent integration:
|
|
273
|
-
|
|
274
156
|
```bash
|
|
275
157
|
mpx-db --schema
|
|
276
158
|
```
|
|
277
159
|
|
|
278
|
-
Returns a
|
|
279
|
-
|
|
280
|
-
### MCP (Model Context Protocol) Server
|
|
281
|
-
|
|
282
|
-
Run `mpx-db` as an MCP server for seamless AI agent integration:
|
|
160
|
+
Returns a complete JSON schema describing all commands, flags, inputs, outputs, and examples.
|
|
283
161
|
|
|
284
|
-
|
|
285
|
-
mpx-db mcp
|
|
286
|
-
```
|
|
162
|
+
### MCP Integration
|
|
287
163
|
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
Add to your Claude Desktop config (`~/Library/Application Support/Claude/claude_desktop_config.json`):
|
|
164
|
+
Add to your MCP client configuration (Claude Desktop, Cursor, Windsurf, etc.):
|
|
291
165
|
|
|
292
166
|
```json
|
|
293
167
|
{
|
|
@@ -300,181 +174,84 @@ Add to your Claude Desktop config (`~/Library/Application Support/Claude/claude_
|
|
|
300
174
|
}
|
|
301
175
|
```
|
|
302
176
|
|
|
303
|
-
|
|
177
|
+
The MCP server exposes these tools:
|
|
178
|
+
- **`query`** โ Execute SQL queries
|
|
179
|
+
- **`list_tables`** โ Get all tables with row counts
|
|
180
|
+
- **`describe_table`** โ Show table schema
|
|
181
|
+
- **`get_info`** โ Database information
|
|
182
|
+
- **`export_table`** โ Export table data as JSON
|
|
183
|
+
- **`get_schema`** โ Get full command schema
|
|
304
184
|
|
|
305
|
-
|
|
185
|
+
### Exit Codes
|
|
306
186
|
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
- `export_table` โ Export table data as JSON
|
|
312
|
-
- `get_schema` โ Get full command schema
|
|
187
|
+
| Code | Meaning |
|
|
188
|
+
|------|---------|
|
|
189
|
+
| 0 | Success |
|
|
190
|
+
| 1 | Error (connection failed, query failed, etc.) |
|
|
313
191
|
|
|
314
|
-
###
|
|
192
|
+
### PDF Reports
|
|
315
193
|
|
|
316
|
-
|
|
194
|
+
Generate professional PDF reports from queries and schema dumps:
|
|
317
195
|
|
|
318
196
|
```bash
|
|
319
|
-
#
|
|
320
|
-
mpx-db
|
|
321
|
-
```
|
|
322
|
-
|
|
323
|
-
### Exit Codes
|
|
324
|
-
|
|
325
|
-
Predictable exit codes for CI/CD and scripting:
|
|
197
|
+
# Schema report โ full database structure as PDF
|
|
198
|
+
mpx-db schema dump <connection> --pdf schema-report.pdf
|
|
326
199
|
|
|
327
|
-
|
|
328
|
-
-
|
|
329
|
-
|
|
330
|
-
```bash
|
|
331
|
-
#!/bin/bash
|
|
332
|
-
if mpx-db query dev "SELECT 1" --quiet; then
|
|
333
|
-
echo "Database is up"
|
|
334
|
-
else
|
|
335
|
-
echo "Database is down"
|
|
336
|
-
exit 1
|
|
337
|
-
fi
|
|
200
|
+
# Query results as PDF
|
|
201
|
+
mpx-db query <connection> "SELECT * FROM users" --pdf results.pdf
|
|
338
202
|
```
|
|
339
203
|
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
```javascript
|
|
343
|
-
// AI agent discovers available commands
|
|
344
|
-
const schema = await exec('mpx-db --schema');
|
|
204
|
+
PDFs include formatted tables, database metadata, and Mesaplex branding. Great for sharing with teammates or archiving.
|
|
345
205
|
|
|
346
|
-
|
|
347
|
-
const result = await exec('mpx-db query dev "SELECT * FROM orders WHERE status = \'pending\'" --json');
|
|
348
|
-
const orders = JSON.parse(result.stdout);
|
|
206
|
+
### Automation Tips
|
|
349
207
|
|
|
350
|
-
|
|
351
|
-
|
|
208
|
+
- Use `--json` for machine-parseable output
|
|
209
|
+
- Use `--pdf <file>` for formatted reports
|
|
210
|
+
- Use `--quiet` to suppress banners and progress info
|
|
211
|
+
- Pipe output to `jq` for filtering
|
|
212
|
+
- Check exit codes for pass/fail in CI/CD
|
|
352
213
|
|
|
353
|
-
|
|
354
|
-
await exec('mpx-db migrate up dev --json --quiet');
|
|
355
|
-
```
|
|
214
|
+
## Database Support
|
|
356
215
|
|
|
357
|
-
|
|
216
|
+
| Database | Driver Package | Notes |
|
|
217
|
+
|------------|-------------------|--------------------------|
|
|
218
|
+
| SQLite | better-sqlite3 | File-based, great for dev|
|
|
219
|
+
| PostgreSQL | pg | Full support |
|
|
220
|
+
| MySQL | mysql2 | Full support |
|
|
358
221
|
|
|
359
|
-
|
|
360
|
-
mpx-db/
|
|
361
|
-
โโโ bin/
|
|
362
|
-
โ โโโ mpx-db.js # CLI entry point
|
|
363
|
-
โโโ src/
|
|
364
|
-
โ โโโ cli.js # Command definitions
|
|
365
|
-
โ โโโ commands/ # Command implementations
|
|
366
|
-
โ โ โโโ connections.js
|
|
367
|
-
โ โ โโโ query.js
|
|
368
|
-
โ โ โโโ schema.js
|
|
369
|
-
โ โ โโโ migrate.js
|
|
370
|
-
โ โ โโโ data.js
|
|
371
|
-
โ โโโ db/ # Database adapters
|
|
372
|
-
โ โ โโโ base-adapter.js
|
|
373
|
-
โ โ โโโ sqlite-adapter.js
|
|
374
|
-
โ โ โโโ postgres-adapter.js
|
|
375
|
-
โ โ โโโ mysql-adapter.js
|
|
376
|
-
โ โ โโโ connection.js
|
|
377
|
-
โ โโโ utils/ # Utilities
|
|
378
|
-
โ โโโ crypto.js # Credential encryption
|
|
379
|
-
โ โโโ config.js # Config management
|
|
380
|
-
โโโ test/ # Test suite
|
|
381
|
-
```
|
|
222
|
+
Drivers are optional peer dependencies โ install only what you need. If a driver is missing, you'll get a helpful error message.
|
|
382
223
|
|
|
383
224
|
## Security
|
|
384
225
|
|
|
385
226
|
- **Encrypted credentials** โ Connection strings with passwords are encrypted using AES-256-GCM
|
|
386
227
|
- **Local storage** โ Credentials stored in `~/.mpx-db/connections.json` with 600 permissions
|
|
387
|
-
- **Key management** โ Encryption key
|
|
388
|
-
|
|
389
|
-
โ ๏ธ **Note:** While credentials are encrypted at rest, this is not a substitute for proper secrets management in production. For production deployments, use environment variables or a secrets manager.
|
|
390
|
-
|
|
391
|
-
## Database Support
|
|
392
|
-
|
|
393
|
-
| Database | Status | Driver Package | Notes |
|
|
394
|
-
|------------|--------|-------------------|--------------------------|
|
|
395
|
-
| SQLite | โ
| better-sqlite3 | File-based, great for dev|
|
|
396
|
-
| PostgreSQL | โ
| pg | Full support |
|
|
397
|
-
| MySQL | โ
| mysql2 | Full support |
|
|
398
|
-
|
|
399
|
-
**Note:** Database drivers are optional peer dependencies. Install only what you need:
|
|
400
|
-
|
|
401
|
-
```bash
|
|
402
|
-
npm install -g better-sqlite3 # For SQLite
|
|
403
|
-
npm install -g pg # For PostgreSQL
|
|
404
|
-
npm install -g mysql2 # For MySQL
|
|
405
|
-
```
|
|
406
|
-
|
|
407
|
-
If you try to connect without the required driver, you'll get a helpful error message:
|
|
228
|
+
- **Key management** โ Encryption key in `~/.mpx-db/.key` (auto-generated)
|
|
408
229
|
|
|
409
|
-
|
|
410
|
-
โ SQLite driver not found. Install it with:
|
|
411
|
-
npm install better-sqlite3
|
|
412
|
-
```
|
|
413
|
-
|
|
414
|
-
## Testing
|
|
415
|
-
|
|
416
|
-
```bash
|
|
417
|
-
# Run all tests
|
|
418
|
-
npm test
|
|
419
|
-
|
|
420
|
-
# Run tests in watch mode
|
|
421
|
-
npm run test:watch
|
|
422
|
-
```
|
|
423
|
-
|
|
424
|
-
Test suite includes:
|
|
425
|
-
- Connection management (4 tests)
|
|
426
|
-
- Schema operations (4 tests)
|
|
427
|
-
- Query operations (5 tests)
|
|
428
|
-
- Migrations (4 tests)
|
|
429
|
-
- Data export (3 tests)
|
|
230
|
+
โ ๏ธ For production, use environment variables or a secrets manager rather than saved connections.
|
|
430
231
|
|
|
431
|
-
|
|
232
|
+
## Free vs Pro
|
|
432
233
|
|
|
433
|
-
|
|
234
|
+
All features are currently available in the free tier.
|
|
434
235
|
|
|
435
|
-
**
|
|
236
|
+
**Upgrade to Pro:** [https://mesaplex.com/mpx-db](https://mesaplex.com/mpx-db)
|
|
436
237
|
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
**Inspiration:** Tools like [Skeema](https://www.skeema.io/) prove this model works. But Skeema is MySQL-only and expensive for small teams. `mpx-db` is open source, multi-database, and focused on developer ergonomics.
|
|
440
|
-
|
|
441
|
-
## Roadmap
|
|
442
|
-
|
|
443
|
-
**v1.1 (Current)** โ
|
|
444
|
-
- SQLite, PostgreSQL, MySQL support
|
|
445
|
-
- Connection management
|
|
446
|
-
- Query execution with beautiful output
|
|
447
|
-
- Schema inspection (dump, describe, tables, info)
|
|
448
|
-
- Migration system (create, up, down, status)
|
|
449
|
-
- Data export (JSON, CSV)
|
|
450
|
-
- **AI-native features:** JSON output (`--json`), schema discovery (`--schema`), MCP server mode
|
|
451
|
-
- **Quiet mode** (`--quiet`) for scripting
|
|
452
|
-
- Predictable exit codes
|
|
453
|
-
|
|
454
|
-
**v1.2 (Planned)**
|
|
455
|
-
- Interactive query REPL mode
|
|
456
|
-
- Query history and favorites
|
|
457
|
-
- Auto-complete for table/column names
|
|
458
|
-
- Migration templates (create table, add column, etc.)
|
|
238
|
+
## License
|
|
459
239
|
|
|
460
|
-
|
|
461
|
-
- Schema diff between environments
|
|
462
|
-
- Auto-generate migrations from schema changes
|
|
463
|
-
- Visual schema diagrams (ASCII art)
|
|
464
|
-
- Data seeding from JSON/CSV
|
|
465
|
-
- Database backup & restore
|
|
466
|
-
- Support for MongoDB, Redis
|
|
240
|
+
Dual License โ Free tier for personal use, Pro license for commercial use and advanced features. See [LICENSE](LICENSE) for full terms.
|
|
467
241
|
|
|
468
|
-
##
|
|
242
|
+
## Links
|
|
469
243
|
|
|
470
|
-
|
|
244
|
+
- **Website:** [https://mesaplex.com](https://mesaplex.com)
|
|
245
|
+
- **npm:** [https://www.npmjs.com/package/mpx-db](https://www.npmjs.com/package/mpx-db)
|
|
246
|
+
- **GitHub:** [https://github.com/mesaplexdev/mpx-db](https://github.com/mesaplexdev/mpx-db)
|
|
247
|
+
- **Support:** support@mesaplex.com
|
|
471
248
|
|
|
472
|
-
|
|
249
|
+
### Related Tools
|
|
473
250
|
|
|
474
|
-
|
|
251
|
+
- **[mpx-scan](https://www.npmjs.com/package/mpx-scan)** โ Website security scanner
|
|
252
|
+
- **[mpx-api](https://www.npmjs.com/package/mpx-api)** โ API testing, mocking, and documentation
|
|
253
|
+
- **[mpx-secrets-audit](https://www.npmjs.com/package/mpx-secrets-audit)** โ Secret lifecycle tracking and audit
|
|
475
254
|
|
|
476
255
|
---
|
|
477
256
|
|
|
478
|
-
**
|
|
479
|
-
|
|
480
|
-
**Made by Mesaplex** โ Build tools that actually work.
|
|
257
|
+
**Made with โค๏ธ by [Mesaplex](https://mesaplex.com)**
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
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"
|
|
@@ -12,20 +12,23 @@
|
|
|
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
34
|
"license": "SEE LICENSE IN LICENSE",
|
|
@@ -34,7 +37,9 @@
|
|
|
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
|
},
|
|
@@ -43,7 +48,8 @@
|
|
|
43
48
|
"better-sqlite3": "^12.6.2",
|
|
44
49
|
"chalk": "^5.3.0",
|
|
45
50
|
"cli-table3": "^0.6.5",
|
|
46
|
-
"commander": "^12.1.0"
|
|
51
|
+
"commander": "^12.1.0",
|
|
52
|
+
"pdfkit": "^0.17.2"
|
|
47
53
|
},
|
|
48
54
|
"peerDependencies": {
|
|
49
55
|
"mysql2": "^3.11.5",
|
|
@@ -62,6 +68,6 @@
|
|
|
62
68
|
"bin/",
|
|
63
69
|
"README.md",
|
|
64
70
|
"LICENSE",
|
|
65
|
-
"
|
|
71
|
+
"CHANGELOG.md"
|
|
66
72
|
]
|
|
67
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
|
}
|
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',
|
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
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));
|
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PDF Report Generator for mpx-db
|
|
3
|
+
*
|
|
4
|
+
* Generates professional database reports using PDFKit.
|
|
5
|
+
* Style consistent with mpx-scan and mpx-api PDF reports.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import PDFDocument from 'pdfkit';
|
|
9
|
+
import { createWriteStream, readFileSync } from 'fs';
|
|
10
|
+
import { fileURLToPath } from 'url';
|
|
11
|
+
import { dirname, join } from 'path';
|
|
12
|
+
|
|
13
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
14
|
+
const __dirname = dirname(__filename);
|
|
15
|
+
const pkg = JSON.parse(readFileSync(join(__dirname, '../../package.json'), 'utf8'));
|
|
16
|
+
|
|
17
|
+
// Color palette (consistent with mpx-scan / mpx-api)
|
|
18
|
+
const COLORS = {
|
|
19
|
+
primary: '#1a56db',
|
|
20
|
+
dark: '#1f2937',
|
|
21
|
+
gray: '#6b7280',
|
|
22
|
+
lightGray: '#e5e7eb',
|
|
23
|
+
white: '#ffffff',
|
|
24
|
+
pass: '#16a34a',
|
|
25
|
+
warn: '#ea580c',
|
|
26
|
+
fail: '#dc2626',
|
|
27
|
+
headerBg: '#1e3a5f',
|
|
28
|
+
sectionBg: '#f3f4f6',
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Check for page overflow and add page if needed
|
|
33
|
+
*/
|
|
34
|
+
function checkPage(doc, minSpace = 120) {
|
|
35
|
+
if (doc.y > doc.page.height - minSpace) {
|
|
36
|
+
doc.addPage();
|
|
37
|
+
doc.y = 50;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Draw consistent header bar
|
|
43
|
+
*/
|
|
44
|
+
function drawHeader(doc, title, subtitle) {
|
|
45
|
+
const pageWidth = doc.page.width - doc.page.margins.left - doc.page.margins.right;
|
|
46
|
+
doc.rect(0, 0, doc.page.width, 100).fill(COLORS.headerBg);
|
|
47
|
+
doc.fontSize(22).fillColor(COLORS.white).font('Helvetica-Bold')
|
|
48
|
+
.text(title, 50, 30);
|
|
49
|
+
doc.fontSize(10).fillColor('#a0b4cc').font('Helvetica')
|
|
50
|
+
.text(subtitle, 50, 60);
|
|
51
|
+
doc.y = 120;
|
|
52
|
+
return pageWidth;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Draw footers on all pages
|
|
57
|
+
*/
|
|
58
|
+
function drawFooters(doc, pageWidth) {
|
|
59
|
+
const now = new Date().toLocaleDateString('en-US', {
|
|
60
|
+
year: 'numeric', month: 'long', day: 'numeric',
|
|
61
|
+
hour: '2-digit', minute: '2-digit',
|
|
62
|
+
});
|
|
63
|
+
const range = doc.bufferedPageRange();
|
|
64
|
+
for (let i = range.start; i < range.start + range.count; i++) {
|
|
65
|
+
doc.switchToPage(i);
|
|
66
|
+
const footerY = doc.page.height - 35;
|
|
67
|
+
doc.fontSize(7).fillColor(COLORS.gray).font('Helvetica')
|
|
68
|
+
.text(
|
|
69
|
+
`Generated by mpx-db v${pkg.version} on ${now}`,
|
|
70
|
+
50, footerY, { width: pageWidth, align: 'center' }
|
|
71
|
+
);
|
|
72
|
+
doc.text(
|
|
73
|
+
`Page ${i + 1} of ${range.count}`,
|
|
74
|
+
50, footerY + 12, { width: pageWidth, align: 'center' }
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Draw a data table
|
|
81
|
+
*/
|
|
82
|
+
function drawTable(doc, headers, rows, pageWidth, options = {}) {
|
|
83
|
+
const colWidths = options.colWidths || headers.map(() => pageWidth / headers.length);
|
|
84
|
+
const startX = 50;
|
|
85
|
+
|
|
86
|
+
// Header row
|
|
87
|
+
checkPage(doc, 80);
|
|
88
|
+
let x = startX;
|
|
89
|
+
doc.roundedRect(startX, doc.y, pageWidth, 22, 3).fill(COLORS.primary);
|
|
90
|
+
for (let i = 0; i < headers.length; i++) {
|
|
91
|
+
doc.fontSize(9).fillColor(COLORS.white).font('Helvetica-Bold')
|
|
92
|
+
.text(headers[i], x + 6, doc.y + 5, { width: colWidths[i] - 12 });
|
|
93
|
+
x += colWidths[i];
|
|
94
|
+
}
|
|
95
|
+
doc.y += 26;
|
|
96
|
+
|
|
97
|
+
// Data rows
|
|
98
|
+
for (let r = 0; r < rows.length; r++) {
|
|
99
|
+
checkPage(doc, 40);
|
|
100
|
+
const row = rows[r];
|
|
101
|
+
const bg = r % 2 === 0 ? COLORS.sectionBg : COLORS.white;
|
|
102
|
+
|
|
103
|
+
// Calculate row height
|
|
104
|
+
let maxH = 16;
|
|
105
|
+
for (let i = 0; i < row.length; i++) {
|
|
106
|
+
const h = doc.heightOfString(String(row[i] ?? ''), { width: colWidths[i] - 12, fontSize: 8 });
|
|
107
|
+
if (h + 8 > maxH) maxH = h + 8;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
doc.rect(startX, doc.y, pageWidth, maxH).fill(bg);
|
|
111
|
+
x = startX;
|
|
112
|
+
for (let i = 0; i < row.length; i++) {
|
|
113
|
+
const val = row[i] === null || row[i] === undefined ? 'โ' : String(row[i]);
|
|
114
|
+
const color = val === 'โ' ? COLORS.gray : COLORS.dark;
|
|
115
|
+
doc.fontSize(8).fillColor(color).font('Helvetica')
|
|
116
|
+
.text(val, x + 6, doc.y + 4, { width: colWidths[i] - 12 });
|
|
117
|
+
x += colWidths[i];
|
|
118
|
+
}
|
|
119
|
+
doc.y += maxH;
|
|
120
|
+
}
|
|
121
|
+
doc.y += 10;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Generate schema report PDF
|
|
126
|
+
*/
|
|
127
|
+
export function generateSchemaPDF(dbInfo, tables, tableSchemas, outputPath) {
|
|
128
|
+
return new Promise((resolve, reject) => {
|
|
129
|
+
try {
|
|
130
|
+
const now = new Date().toLocaleDateString('en-US', {
|
|
131
|
+
year: 'numeric', month: 'long', day: 'numeric',
|
|
132
|
+
hour: '2-digit', minute: '2-digit',
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
const doc = new PDFDocument({
|
|
136
|
+
size: 'A4',
|
|
137
|
+
margins: { top: 50, bottom: 50, left: 50, right: 50 },
|
|
138
|
+
info: {
|
|
139
|
+
Title: `mpx-db Schema Report โ ${dbInfo.database || dbInfo.path || 'Database'}`,
|
|
140
|
+
Author: 'mpx-db',
|
|
141
|
+
Subject: 'Database Schema Report',
|
|
142
|
+
Creator: `mpx-db v${pkg.version}`,
|
|
143
|
+
},
|
|
144
|
+
bufferPages: true,
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
const stream = createWriteStream(outputPath);
|
|
148
|
+
doc.pipe(stream);
|
|
149
|
+
|
|
150
|
+
const dbName = dbInfo.database || dbInfo.path || 'Database';
|
|
151
|
+
const pageWidth = drawHeader(doc,
|
|
152
|
+
'mpx-db Schema Report',
|
|
153
|
+
`v${pkg.version} โข ${now} โข ${dbName}`
|
|
154
|
+
);
|
|
155
|
+
|
|
156
|
+
// โโโ Summary Box โโโ
|
|
157
|
+
doc.roundedRect(50, doc.y, pageWidth, 80, 6).fill(COLORS.sectionBg);
|
|
158
|
+
const summaryTop = doc.y + 12;
|
|
159
|
+
|
|
160
|
+
doc.circle(100, summaryTop + 28, 26).fill(COLORS.primary);
|
|
161
|
+
doc.fontSize(20).fillColor(COLORS.white).font('Helvetica-Bold')
|
|
162
|
+
.text(String(dbInfo.tables), 100 - 20, summaryTop + 16, { width: 40, align: 'center' });
|
|
163
|
+
|
|
164
|
+
doc.fontSize(14).fillColor(COLORS.dark).font('Helvetica-Bold')
|
|
165
|
+
.text(`${dbInfo.type} Database`, 145, summaryTop + 5);
|
|
166
|
+
doc.fontSize(10).fillColor(COLORS.gray).font('Helvetica')
|
|
167
|
+
.text(`${dbInfo.tables} tables โข ${dbInfo.totalRows.toLocaleString()} total rows โข ${dbInfo.sizeFormatted}`, 145, summaryTop + 25);
|
|
168
|
+
|
|
169
|
+
doc.y = summaryTop + 68;
|
|
170
|
+
|
|
171
|
+
// โโโ Tables Overview โโโ
|
|
172
|
+
doc.y += 10;
|
|
173
|
+
doc.fontSize(14).fillColor(COLORS.dark).font('Helvetica-Bold')
|
|
174
|
+
.text('Tables', 50, doc.y);
|
|
175
|
+
doc.y += 20;
|
|
176
|
+
|
|
177
|
+
const tableRows = tables.map(t => [t.name, t.type, t.rows.toLocaleString()]);
|
|
178
|
+
drawTable(doc, ['Table', 'Type', 'Rows'], tableRows, pageWidth, {
|
|
179
|
+
colWidths: [pageWidth * 0.5, pageWidth * 0.25, pageWidth * 0.25]
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
// โโโ Table Schemas โโโ
|
|
183
|
+
for (const { name, columns } of tableSchemas) {
|
|
184
|
+
checkPage(doc, 100);
|
|
185
|
+
|
|
186
|
+
doc.y += 5;
|
|
187
|
+
doc.roundedRect(50, doc.y, pageWidth, 26, 4).fill(COLORS.headerBg);
|
|
188
|
+
doc.fontSize(11).fillColor(COLORS.white).font('Helvetica-Bold')
|
|
189
|
+
.text(name, 60, doc.y + 7);
|
|
190
|
+
doc.fontSize(9).fillColor('#a0b4cc').font('Helvetica')
|
|
191
|
+
.text(`${columns.length} columns`, 60, doc.y + 7, { width: pageWidth - 20, align: 'right' });
|
|
192
|
+
doc.y += 32;
|
|
193
|
+
|
|
194
|
+
const colRows = columns.map(c => [
|
|
195
|
+
c.name,
|
|
196
|
+
c.type,
|
|
197
|
+
c.nullable ? 'YES' : 'NO',
|
|
198
|
+
c.default || 'โ',
|
|
199
|
+
c.primaryKey ? 'PRI' : 'โ'
|
|
200
|
+
]);
|
|
201
|
+
|
|
202
|
+
drawTable(doc, ['Column', 'Type', 'Nullable', 'Default', 'Key'], colRows, pageWidth, {
|
|
203
|
+
colWidths: [pageWidth * 0.28, pageWidth * 0.22, pageWidth * 0.15, pageWidth * 0.2, pageWidth * 0.15]
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
drawFooters(doc, pageWidth);
|
|
208
|
+
doc.end();
|
|
209
|
+
|
|
210
|
+
stream.on('finish', () => resolve(outputPath));
|
|
211
|
+
stream.on('error', reject);
|
|
212
|
+
} catch (err) {
|
|
213
|
+
reject(err);
|
|
214
|
+
}
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Generate query results PDF
|
|
220
|
+
*/
|
|
221
|
+
export function generateQueryPDF(rows, meta, outputPath) {
|
|
222
|
+
return new Promise((resolve, reject) => {
|
|
223
|
+
try {
|
|
224
|
+
const now = new Date().toLocaleDateString('en-US', {
|
|
225
|
+
year: 'numeric', month: 'long', day: 'numeric',
|
|
226
|
+
hour: '2-digit', minute: '2-digit',
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
const doc = new PDFDocument({
|
|
230
|
+
size: 'A4',
|
|
231
|
+
layout: rows.length > 0 && Object.keys(rows[0]).length > 5 ? 'landscape' : 'portrait',
|
|
232
|
+
margins: { top: 50, bottom: 50, left: 50, right: 50 },
|
|
233
|
+
info: {
|
|
234
|
+
Title: `mpx-db Query Report`,
|
|
235
|
+
Author: 'mpx-db',
|
|
236
|
+
Subject: 'Database Query Results',
|
|
237
|
+
Creator: `mpx-db v${pkg.version}`,
|
|
238
|
+
},
|
|
239
|
+
bufferPages: true,
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
const stream = createWriteStream(outputPath);
|
|
243
|
+
doc.pipe(stream);
|
|
244
|
+
|
|
245
|
+
const pageWidth = drawHeader(doc,
|
|
246
|
+
'mpx-db Query Report',
|
|
247
|
+
`v${pkg.version} โข ${now}`
|
|
248
|
+
);
|
|
249
|
+
|
|
250
|
+
// Query info
|
|
251
|
+
doc.roundedRect(50, doc.y, pageWidth, 60, 6).fill(COLORS.sectionBg);
|
|
252
|
+
const summaryTop = doc.y + 10;
|
|
253
|
+
|
|
254
|
+
doc.fontSize(9).fillColor(COLORS.gray).font('Helvetica').text('SQL Query:', 60, summaryTop);
|
|
255
|
+
doc.fontSize(9).fillColor(COLORS.dark).font('Helvetica-Bold')
|
|
256
|
+
.text(meta.sql || 'โ', 60, summaryTop + 14, { width: pageWidth - 20 });
|
|
257
|
+
|
|
258
|
+
doc.fontSize(9).fillColor(COLORS.gray).font('Helvetica')
|
|
259
|
+
.text(`${rows.length} row(s) โข ${meta.duration || 0}ms`, 60, summaryTop + 34);
|
|
260
|
+
|
|
261
|
+
doc.y = summaryTop + 56;
|
|
262
|
+
|
|
263
|
+
if (rows.length === 0) {
|
|
264
|
+
doc.y += 20;
|
|
265
|
+
doc.fontSize(12).fillColor(COLORS.gray).font('Helvetica')
|
|
266
|
+
.text('No rows returned.', 50, doc.y, { width: pageWidth, align: 'center' });
|
|
267
|
+
} else {
|
|
268
|
+
doc.y += 10;
|
|
269
|
+
const columns = Object.keys(rows[0]);
|
|
270
|
+
const colWidth = pageWidth / columns.length;
|
|
271
|
+
const dataRows = rows.map(row => columns.map(c => row[c]));
|
|
272
|
+
drawTable(doc, columns, dataRows, pageWidth, {
|
|
273
|
+
colWidths: columns.map(() => colWidth)
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
drawFooters(doc, pageWidth);
|
|
278
|
+
doc.end();
|
|
279
|
+
|
|
280
|
+
stream.on('finish', () => resolve(outputPath));
|
|
281
|
+
stream.on('error', reject);
|
|
282
|
+
} catch (err) {
|
|
283
|
+
reject(err);
|
|
284
|
+
}
|
|
285
|
+
});
|
|
286
|
+
}
|
package/src/schema.js
CHANGED
|
@@ -139,7 +139,7 @@ export function getSchema() {
|
|
|
139
139
|
},
|
|
140
140
|
query: {
|
|
141
141
|
description: 'Execute a SQL query',
|
|
142
|
-
usage: 'mpx-db query <target> <sql> [--json]',
|
|
142
|
+
usage: 'mpx-db query <target> <sql> [--json] [--pdf file.pdf]',
|
|
143
143
|
arguments: {
|
|
144
144
|
target: {
|
|
145
145
|
type: 'string',
|
|
@@ -158,6 +158,10 @@ export function getSchema() {
|
|
|
158
158
|
default: false,
|
|
159
159
|
description: 'Output results as JSON'
|
|
160
160
|
},
|
|
161
|
+
'--pdf': {
|
|
162
|
+
type: 'string',
|
|
163
|
+
description: 'Export query results as a formatted PDF report'
|
|
164
|
+
},
|
|
161
165
|
'--quiet': {
|
|
162
166
|
type: 'boolean',
|
|
163
167
|
default: false,
|
|
@@ -318,7 +322,7 @@ export function getSchema() {
|
|
|
318
322
|
},
|
|
319
323
|
'schema dump': {
|
|
320
324
|
description: 'Dump database schema as SQL',
|
|
321
|
-
usage: 'mpx-db schema dump <target>',
|
|
325
|
+
usage: 'mpx-db schema dump <target> [--pdf file.pdf]',
|
|
322
326
|
arguments: {
|
|
323
327
|
target: {
|
|
324
328
|
type: 'string',
|
|
@@ -332,6 +336,10 @@ export function getSchema() {
|
|
|
332
336
|
default: false,
|
|
333
337
|
description: 'Output as JSON with SQL content'
|
|
334
338
|
},
|
|
339
|
+
'--pdf': {
|
|
340
|
+
type: 'string',
|
|
341
|
+
description: 'Export schema as a formatted PDF report'
|
|
342
|
+
},
|
|
335
343
|
'--quiet': {
|
|
336
344
|
type: 'boolean',
|
|
337
345
|
default: false,
|
|
@@ -586,6 +594,35 @@ export function getSchema() {
|
|
|
586
594
|
}
|
|
587
595
|
}
|
|
588
596
|
},
|
|
597
|
+
globalFlags: {
|
|
598
|
+
'--json': {
|
|
599
|
+
type: 'boolean',
|
|
600
|
+
default: false,
|
|
601
|
+
description: 'Output results as structured JSON'
|
|
602
|
+
},
|
|
603
|
+
'--pdf': {
|
|
604
|
+
type: 'string',
|
|
605
|
+
description: 'Export results as a formatted PDF report (supported by query, schema dump)'
|
|
606
|
+
},
|
|
607
|
+
'-q, --quiet': {
|
|
608
|
+
type: 'boolean',
|
|
609
|
+
default: false,
|
|
610
|
+
description: 'Suppress non-essential output'
|
|
611
|
+
},
|
|
612
|
+
'--schema': {
|
|
613
|
+
type: 'boolean',
|
|
614
|
+
default: false,
|
|
615
|
+
description: 'Output this schema as JSON'
|
|
616
|
+
},
|
|
617
|
+
'--version': {
|
|
618
|
+
type: 'boolean',
|
|
619
|
+
description: 'Show version number'
|
|
620
|
+
},
|
|
621
|
+
'--help': {
|
|
622
|
+
type: 'boolean',
|
|
623
|
+
description: 'Show help information'
|
|
624
|
+
}
|
|
625
|
+
},
|
|
589
626
|
exitCodes: {
|
|
590
627
|
0: 'Success',
|
|
591
628
|
1: 'Error (connection failed, query failed, etc.)'
|