database-studio 1.0.6 → 1.0.7
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 +80 -44
- package/dist/README.md +80 -44
- package/dist/cli.cjs +6 -15
- package/dist/server/index.cjs +6 -15
- package/dist/spa/assets/{index-KNNj2aN0.js → index-BKJAq2p6.js} +11 -11
- package/dist/spa/index.html +1 -1
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -1,61 +1,82 @@
|
|
|
1
|
-
# Database Studio
|
|
1
|
+
# Database Studio 🚀
|
|
2
2
|
|
|
3
|
-
[](#tech-stack)
|
|
3
|
+
[](./package.json)
|
|
4
|
+
[](#license)
|
|
5
|
+
[](#tech-stack)
|
|
6
6
|
|
|
7
|
-
**Database Studio
|
|
7
|
+
**Database Studio** is a high-performance, full-stack database viewer and schema explorer. Designed for developers and DBAs, it provides a modern interface to explore, analyze, and document your **MySQL** and **MongoDB** databases.
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npx database-studio
|
|
11
|
+
```
|
|
8
12
|
|
|
9
13
|
---
|
|
10
14
|
|
|
11
15
|
## 🌟 Key Features
|
|
12
16
|
|
|
13
|
-
### 📊 Interactive
|
|
14
|
-
|
|
17
|
+
### 📊 Interactive Data Viewer
|
|
18
|
+
Browse your data with a paginated table interface. Works with MySQL tables and MongoDB collections seamlessly.
|
|
15
19
|
|
|
16
20
|
### 📐 Visual Schema Explorer
|
|
17
|
-
Visualize your entire database architecture at a glance.
|
|
21
|
+
Visualize your entire database architecture at a glance. Grid-based **Schema View** with interactive cards and dynamic color-coding. For MongoDB, schemas are automatically inferred by sampling collection documents.
|
|
22
|
+
|
|
23
|
+
### 🎨 Canvas View
|
|
24
|
+
Drag-and-drop schema visualization with table relationships rendered as an interactive canvas.
|
|
18
25
|
|
|
19
|
-
### 📄
|
|
20
|
-
Generate
|
|
26
|
+
### 📄 PDF Export
|
|
27
|
+
Generate professional A4 schema documentation with a single click. Powered by Puppeteer server-side rendering with full style fidelity.
|
|
21
28
|
|
|
22
|
-
### 🔐
|
|
23
|
-
-
|
|
24
|
-
-
|
|
25
|
-
-
|
|
29
|
+
### 🔐 License-Gated Access
|
|
30
|
+
- JWT session management with HTTP-only cookies
|
|
31
|
+
- RSA-256 offline verification
|
|
32
|
+
- Machine-ID binding for license integrity
|
|
26
33
|
|
|
27
|
-
### 🛠️ Developer-First
|
|
28
|
-
-
|
|
29
|
-
-
|
|
30
|
-
-
|
|
34
|
+
### 🛠️ Developer-First
|
|
35
|
+
- Toggle between 1-4 column grid layouts
|
|
36
|
+
- Switch between Data View, Schema View, and Canvas View
|
|
37
|
+
- Starts in setup mode with inline docs when no database is configured
|
|
31
38
|
|
|
32
39
|
---
|
|
33
40
|
|
|
34
41
|
## 🚀 Quick Start
|
|
35
42
|
|
|
36
43
|
### Prerequisites
|
|
37
|
-
- **Node.js
|
|
38
|
-
- **
|
|
39
|
-
- **MySQL**: Access to a running MySQL instance
|
|
44
|
+
- **Node.js** v18+
|
|
45
|
+
- **MySQL** or **MongoDB** instance
|
|
40
46
|
|
|
41
|
-
###
|
|
47
|
+
### Usage via npx
|
|
42
48
|
|
|
43
49
|
```bash
|
|
44
|
-
|
|
45
|
-
|
|
50
|
+
npx database-studio
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### Usage via global install
|
|
46
54
|
|
|
47
|
-
|
|
48
|
-
|
|
55
|
+
```bash
|
|
56
|
+
npm i -g database-studio
|
|
57
|
+
database-studio
|
|
49
58
|
```
|
|
50
59
|
|
|
51
60
|
### Environment Configuration
|
|
52
61
|
|
|
53
|
-
Create a `.env` file in
|
|
62
|
+
Create a `.env` file in your project root with **one** of the following:
|
|
54
63
|
|
|
55
64
|
```env
|
|
65
|
+
# For MySQL
|
|
56
66
|
MYSQL_URL=mysql://user:password@localhost:3306/your_database
|
|
67
|
+
|
|
68
|
+
# For MongoDB (either variable name works)
|
|
69
|
+
MONGODB_URL=mongodb://user:password@localhost:27017/your_database
|
|
70
|
+
MONGODB_URI=mongodb+srv://user:password@cluster.mongodb.net/your_database
|
|
57
71
|
```
|
|
58
72
|
|
|
73
|
+
Optional:
|
|
74
|
+
```env
|
|
75
|
+
PORT=5555 # defaults to 5555
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Database Studio auto-detects which database to connect to based on the env variable present. If neither is set, it starts in **setup mode** showing configuration docs in the browser.
|
|
79
|
+
|
|
59
80
|
---
|
|
60
81
|
|
|
61
82
|
## 💻 Tech Stack
|
|
@@ -63,40 +84,55 @@ MYSQL_URL=mysql://user:password@localhost:3306/your_database
|
|
|
63
84
|
- **Frontend**: [React 18](https://react.dev/), [React Router 6](https://reactrouter.com/), [Tailwind CSS 3](https://tailwindcss.com/)
|
|
64
85
|
- **Backend**: [Express 5](https://expressjs.com/), [Puppeteer](https://pptr.dev/)
|
|
65
86
|
- **UI Components**: [Radix UI](https://www.radix-ui.com/), [Lucide Icons](https://lucide.dev/)
|
|
66
|
-
- **
|
|
67
|
-
- **Build
|
|
87
|
+
- **Databases**: [MySQL](https://www.mysql.com/) via mysql2, [MongoDB](https://www.mongodb.com/) via native driver
|
|
88
|
+
- **Build**: [Vite](https://vitejs.dev/), [tsup](https://tsup.egoist.dev/)
|
|
68
89
|
|
|
69
90
|
---
|
|
70
91
|
|
|
71
|
-
## 🏗️
|
|
92
|
+
## 🏗️ Architecture
|
|
72
93
|
|
|
73
94
|
```text
|
|
74
|
-
client/
|
|
75
|
-
├── components/
|
|
76
|
-
├── pages/
|
|
77
|
-
└── lib/
|
|
95
|
+
client/ # React SPA
|
|
96
|
+
├── components/ # UI & feature components
|
|
97
|
+
├── pages/ # Route-level views
|
|
98
|
+
└── lib/ # Utilities and API wrappers
|
|
99
|
+
|
|
100
|
+
server/ # Express API
|
|
101
|
+
├── index.ts # Routes, license, PDF export
|
|
102
|
+
├── config/ # Server configuration
|
|
103
|
+
└── drivers/ # Database driver abstraction
|
|
104
|
+
├── types.ts # DbDriver interface
|
|
105
|
+
├── mysql.ts # MySQL implementation
|
|
106
|
+
├── mongodb.ts # MongoDB implementation
|
|
107
|
+
└── index.ts # Auto-detection factory
|
|
108
|
+
|
|
109
|
+
bin/ # CLI entry point
|
|
110
|
+
```
|
|
78
111
|
|
|
79
|
-
|
|
80
|
-
├── index.ts # Main entry point with License & PDF logic
|
|
81
|
-
└── config.ts # Server configuration
|
|
112
|
+
### Driver Pattern
|
|
82
113
|
|
|
83
|
-
|
|
84
|
-
|
|
114
|
+
All database operations go through a `DbDriver` interface. The server auto-detects which driver to use based on environment variables — **zero conditionals in route handlers**.
|
|
115
|
+
|
|
116
|
+
| Method | MySQL | MongoDB |
|
|
117
|
+
|--------|-------|---------|
|
|
118
|
+
| `getDatabaseName()` | `SELECT DATABASE()` | `db.databaseName` |
|
|
119
|
+
| `getTables()` | `SHOW TABLES` | `db.listCollections()` |
|
|
120
|
+
| `getTableSchema()` | `SHOW COLUMNS` + FK query | Sample 100 docs, infer types |
|
|
121
|
+
| `getTableData()` | `SELECT * LIMIT OFFSET` | `find().skip().limit()` |
|
|
85
122
|
|
|
86
123
|
---
|
|
87
124
|
|
|
88
125
|
## 📑 PDF Export Engine
|
|
89
126
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
3. **True Fidelity**: Captures full Tailwind colors and styles into a multi-page A4 PDF.
|
|
127
|
+
1. **Headless Navigation**: Puppeteer visits `?view=schema&export=true`
|
|
128
|
+
2. **Adaptive CSS**: Print-specific layout with hidden interactive elements
|
|
129
|
+
3. **Full Fidelity**: Captures Tailwind styles into multi-page A4 PDF
|
|
94
130
|
|
|
95
131
|
---
|
|
96
132
|
|
|
97
133
|
## 🔒 License & Security
|
|
98
134
|
|
|
99
|
-
|
|
135
|
+
Requires a license key on first use. The licensing system uses **RSA-256** signatures and **Machine-ID** hashing.
|
|
100
136
|
|
|
101
137
|
---
|
|
102
138
|
|
package/dist/README.md
CHANGED
|
@@ -1,61 +1,82 @@
|
|
|
1
|
-
# Database Studio
|
|
1
|
+
# Database Studio 🚀
|
|
2
2
|
|
|
3
|
-
[](#tech-stack)
|
|
3
|
+
[](./package.json)
|
|
4
|
+
[](#license)
|
|
5
|
+
[](#tech-stack)
|
|
6
6
|
|
|
7
|
-
**Database Studio
|
|
7
|
+
**Database Studio** is a high-performance, full-stack database viewer and schema explorer. Designed for developers and DBAs, it provides a modern interface to explore, analyze, and document your **MySQL** and **MongoDB** databases.
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npx database-studio
|
|
11
|
+
```
|
|
8
12
|
|
|
9
13
|
---
|
|
10
14
|
|
|
11
15
|
## 🌟 Key Features
|
|
12
16
|
|
|
13
|
-
### 📊 Interactive
|
|
14
|
-
|
|
17
|
+
### 📊 Interactive Data Viewer
|
|
18
|
+
Browse your data with a paginated table interface. Works with MySQL tables and MongoDB collections seamlessly.
|
|
15
19
|
|
|
16
20
|
### 📐 Visual Schema Explorer
|
|
17
|
-
Visualize your entire database architecture at a glance.
|
|
21
|
+
Visualize your entire database architecture at a glance. Grid-based **Schema View** with interactive cards and dynamic color-coding. For MongoDB, schemas are automatically inferred by sampling collection documents.
|
|
22
|
+
|
|
23
|
+
### 🎨 Canvas View
|
|
24
|
+
Drag-and-drop schema visualization with table relationships rendered as an interactive canvas.
|
|
18
25
|
|
|
19
|
-
### 📄
|
|
20
|
-
Generate
|
|
26
|
+
### 📄 PDF Export
|
|
27
|
+
Generate professional A4 schema documentation with a single click. Powered by Puppeteer server-side rendering with full style fidelity.
|
|
21
28
|
|
|
22
|
-
### 🔐
|
|
23
|
-
-
|
|
24
|
-
-
|
|
25
|
-
-
|
|
29
|
+
### 🔐 License-Gated Access
|
|
30
|
+
- JWT session management with HTTP-only cookies
|
|
31
|
+
- RSA-256 offline verification
|
|
32
|
+
- Machine-ID binding for license integrity
|
|
26
33
|
|
|
27
|
-
### 🛠️ Developer-First
|
|
28
|
-
-
|
|
29
|
-
-
|
|
30
|
-
-
|
|
34
|
+
### 🛠️ Developer-First
|
|
35
|
+
- Toggle between 1-4 column grid layouts
|
|
36
|
+
- Switch between Data View, Schema View, and Canvas View
|
|
37
|
+
- Starts in setup mode with inline docs when no database is configured
|
|
31
38
|
|
|
32
39
|
---
|
|
33
40
|
|
|
34
41
|
## 🚀 Quick Start
|
|
35
42
|
|
|
36
43
|
### Prerequisites
|
|
37
|
-
- **Node.js
|
|
38
|
-
- **
|
|
39
|
-
- **MySQL**: Access to a running MySQL instance
|
|
44
|
+
- **Node.js** v18+
|
|
45
|
+
- **MySQL** or **MongoDB** instance
|
|
40
46
|
|
|
41
|
-
###
|
|
47
|
+
### Usage via npx
|
|
42
48
|
|
|
43
49
|
```bash
|
|
44
|
-
|
|
45
|
-
|
|
50
|
+
npx database-studio
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### Usage via global install
|
|
46
54
|
|
|
47
|
-
|
|
48
|
-
|
|
55
|
+
```bash
|
|
56
|
+
npm i -g database-studio
|
|
57
|
+
database-studio
|
|
49
58
|
```
|
|
50
59
|
|
|
51
60
|
### Environment Configuration
|
|
52
61
|
|
|
53
|
-
Create a `.env` file in
|
|
62
|
+
Create a `.env` file in your project root with **one** of the following:
|
|
54
63
|
|
|
55
64
|
```env
|
|
65
|
+
# For MySQL
|
|
56
66
|
MYSQL_URL=mysql://user:password@localhost:3306/your_database
|
|
67
|
+
|
|
68
|
+
# For MongoDB (either variable name works)
|
|
69
|
+
MONGODB_URL=mongodb://user:password@localhost:27017/your_database
|
|
70
|
+
MONGODB_URI=mongodb+srv://user:password@cluster.mongodb.net/your_database
|
|
57
71
|
```
|
|
58
72
|
|
|
73
|
+
Optional:
|
|
74
|
+
```env
|
|
75
|
+
PORT=5555 # defaults to 5555
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Database Studio auto-detects which database to connect to based on the env variable present. If neither is set, it starts in **setup mode** showing configuration docs in the browser.
|
|
79
|
+
|
|
59
80
|
---
|
|
60
81
|
|
|
61
82
|
## 💻 Tech Stack
|
|
@@ -63,40 +84,55 @@ MYSQL_URL=mysql://user:password@localhost:3306/your_database
|
|
|
63
84
|
- **Frontend**: [React 18](https://react.dev/), [React Router 6](https://reactrouter.com/), [Tailwind CSS 3](https://tailwindcss.com/)
|
|
64
85
|
- **Backend**: [Express 5](https://expressjs.com/), [Puppeteer](https://pptr.dev/)
|
|
65
86
|
- **UI Components**: [Radix UI](https://www.radix-ui.com/), [Lucide Icons](https://lucide.dev/)
|
|
66
|
-
- **
|
|
67
|
-
- **Build
|
|
87
|
+
- **Databases**: [MySQL](https://www.mysql.com/) via mysql2, [MongoDB](https://www.mongodb.com/) via native driver
|
|
88
|
+
- **Build**: [Vite](https://vitejs.dev/), [tsup](https://tsup.egoist.dev/)
|
|
68
89
|
|
|
69
90
|
---
|
|
70
91
|
|
|
71
|
-
## 🏗️
|
|
92
|
+
## 🏗️ Architecture
|
|
72
93
|
|
|
73
94
|
```text
|
|
74
|
-
client/
|
|
75
|
-
├── components/
|
|
76
|
-
├── pages/
|
|
77
|
-
└── lib/
|
|
95
|
+
client/ # React SPA
|
|
96
|
+
├── components/ # UI & feature components
|
|
97
|
+
├── pages/ # Route-level views
|
|
98
|
+
└── lib/ # Utilities and API wrappers
|
|
99
|
+
|
|
100
|
+
server/ # Express API
|
|
101
|
+
├── index.ts # Routes, license, PDF export
|
|
102
|
+
├── config/ # Server configuration
|
|
103
|
+
└── drivers/ # Database driver abstraction
|
|
104
|
+
├── types.ts # DbDriver interface
|
|
105
|
+
├── mysql.ts # MySQL implementation
|
|
106
|
+
├── mongodb.ts # MongoDB implementation
|
|
107
|
+
└── index.ts # Auto-detection factory
|
|
108
|
+
|
|
109
|
+
bin/ # CLI entry point
|
|
110
|
+
```
|
|
78
111
|
|
|
79
|
-
|
|
80
|
-
├── index.ts # Main entry point with License & PDF logic
|
|
81
|
-
└── config.ts # Server configuration
|
|
112
|
+
### Driver Pattern
|
|
82
113
|
|
|
83
|
-
|
|
84
|
-
|
|
114
|
+
All database operations go through a `DbDriver` interface. The server auto-detects which driver to use based on environment variables — **zero conditionals in route handlers**.
|
|
115
|
+
|
|
116
|
+
| Method | MySQL | MongoDB |
|
|
117
|
+
|--------|-------|---------|
|
|
118
|
+
| `getDatabaseName()` | `SELECT DATABASE()` | `db.databaseName` |
|
|
119
|
+
| `getTables()` | `SHOW TABLES` | `db.listCollections()` |
|
|
120
|
+
| `getTableSchema()` | `SHOW COLUMNS` + FK query | Sample 100 docs, infer types |
|
|
121
|
+
| `getTableData()` | `SELECT * LIMIT OFFSET` | `find().skip().limit()` |
|
|
85
122
|
|
|
86
123
|
---
|
|
87
124
|
|
|
88
125
|
## 📑 PDF Export Engine
|
|
89
126
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
3. **True Fidelity**: Captures full Tailwind colors and styles into a multi-page A4 PDF.
|
|
127
|
+
1. **Headless Navigation**: Puppeteer visits `?view=schema&export=true`
|
|
128
|
+
2. **Adaptive CSS**: Print-specific layout with hidden interactive elements
|
|
129
|
+
3. **Full Fidelity**: Captures Tailwind styles into multi-page A4 PDF
|
|
94
130
|
|
|
95
131
|
---
|
|
96
132
|
|
|
97
133
|
## 🔒 License & Security
|
|
98
134
|
|
|
99
|
-
|
|
135
|
+
Requires a license key on first use. The licensing system uses **RSA-256** signatures and **Machine-ID** hashing.
|
|
100
136
|
|
|
101
137
|
---
|
|
102
138
|
|
package/dist/cli.cjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
var
|
|
2
|
+
var R=Object.create;var g=Object.defineProperty;var C=Object.getOwnPropertyDescriptor;var _=Object.getOwnPropertyNames;var F=Object.getPrototypeOf,I=Object.prototype.hasOwnProperty;var L=(e,t)=>()=>(t||e((t={exports:{}}).exports,t),t.exports);var q=(e,t,a,n)=>{if(t&&typeof t=="object"||typeof t=="function")for(let r of _(t))!I.call(e,r)&&r!==a&&g(e,r,{get:()=>t[r],enumerable:!(n=C(t,r))||n.enumerable});return e};var h=(e,t,a)=>(a=e!=null?R(F(e)):{},q(t||!e||!e.__esModule?g(a,"default",{value:e,enumerable:!0}):a,e));var M=L((ue,A)=>{var k=Object.create,f=Object.defineProperty,B=Object.getOwnPropertyDescriptor,U=Object.getOwnPropertyNames,P=Object.getPrototypeOf,x=Object.prototype.hasOwnProperty,Y=(e,t,a)=>t in e?f(e,t,{enumerable:!0,configurable:!0,writable:!0,value:a}):e[t]=a,Q=(e,t)=>{for(var a in t)f(e,a,{get:t[a],enumerable:!0})},w=(e,t,a,n)=>{if(t&&typeof t=="object"||typeof t=="function")for(let r of U(t))!x.call(e,r)&&r!==a&&f(e,r,{get:()=>t[r],enumerable:!(n=B(t,r))||n.enumerable});return e},d=(e,t,a)=>(a=e!=null?k(P(e)):{},w(t||!e||!e.__esModule?f(a,"default",{value:e,enumerable:!0}):a,e)),z=e=>w(f({},"__esModule",{value:!0}),e),p=(e,t,a)=>Y(e,typeof t!="symbol"?t+"":t,a),v={};Q(v,{startServer:()=>oe});A.exports=z(v);var y=d(require("express"),1),b=d(require("path"),1),H=d(require("dotenv"),1),K=d(require("puppeteer"),1),$=d(require("cookie-parser"),1),G=d(require("jsonwebtoken"),1),E=d(require("os"),1),V=d(require("crypto"),1),ce=process.env.LICENSE_SERVER_URL||"http://localhost:4000",W=`-----BEGIN PUBLIC KEY-----
|
|
3
3
|
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAoNtHPt9FWow0wkX8QszM
|
|
4
4
|
fsS6raS0edOuhkfJYnoMfSHeQ/cBztIV3kTOqu8N2YOzZhPdOFh8b5lBQMyzShXf
|
|
5
5
|
PpvtCjbW0LzkWYiv1ak33RrV9rh2s599toI+sDv/vMv/msB7RHCNEqaIfeeMYdu4
|
|
@@ -8,18 +8,9 @@ Kik1C6Cth/RHAqznVr75VSkiONlX47cFNXny2L4Ujt20wbVyRtErnYdtZDAaVYv7
|
|
|
8
8
|
ZDHKTqt4L2FyD6WwckqHQwDSUbRjWpMyIwJD7YNg2XSKPldtacuw4VlgD0XaAoM5
|
|
9
9
|
jQIDAQAB
|
|
10
10
|
-----END PUBLIC KEY-----
|
|
11
|
-
`,
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
REFERENCED_TABLE_NAME,
|
|
15
|
-
REFERENCED_COLUMN_NAME
|
|
16
|
-
FROM
|
|
17
|
-
INFORMATION_SCHEMA.KEY_COLUMN_USAGE
|
|
18
|
-
WHERE
|
|
19
|
-
TABLE_NAME = ?
|
|
20
|
-
AND TABLE_SCHEMA = DATABASE()
|
|
21
|
-
AND REFERENCED_TABLE_NAME IS NOT NULL
|
|
22
|
-
`,[o]),f=m.map(u=>({column:u.COLUMN_NAME,referencedTable:u.REFERENCED_TABLE_NAME,referencedColumn:u.REFERENCED_COLUMN_NAME}));a.json({name:o,columns:c,foreignKeys:f})}catch(l){console.error(l),a.status(500).json({error:"Failed to fetch table schema"})}}),n.get("/api/export-pdf",async(i,a)=>{let o=i.cookies?.studio_token;if(!o)return a.status(401).json({error:"Unauthorized"});if(!L(o))return a.status(401).json({error:"Unauthorized"});let l;try{l=await X.default.launch({headless:!0,args:["--no-sandbox","--disable-setuid-sandbox"]});let c=await l.newPage();await c.setCookie({name:"studio_token",value:o,domain:"localhost",path:"/"}),await c.emulateMediaType("print"),await c.addStyleTag({content:`
|
|
11
|
+
`,X=d(require("mysql2/promise"),1),Z=class{constructor(e){this.url=e,p(this,"type","mysql"),p(this,"connection")}async connect(){this.connection=await X.default.createConnection(this.url)}async getDatabaseName(){let[e]=await this.connection.query("SELECT DATABASE()");return e.map(t=>Object.values(t)[0])[0]}async getTables(){let[e]=await this.connection.query("SHOW TABLES");return e.map(t=>Object.values(t)[0])}async getTableSchema(e){let t=await this.validateTableName(e),[a]=await this.connection.query(`SHOW COLUMNS FROM \`${t}\``),n=a.map(l=>({name:l.Field,type:l.Type,nullable:l.Null==="YES",key:l.Key,default:l.Default,extra:l.Extra})),[r]=await this.connection.query(`SELECT COLUMN_NAME, REFERENCED_TABLE_NAME, REFERENCED_COLUMN_NAME
|
|
12
|
+
FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE
|
|
13
|
+
WHERE TABLE_NAME = ? AND TABLE_SCHEMA = DATABASE() AND REFERENCED_TABLE_NAME IS NOT NULL`,[t]),c=r.map(l=>({column:l.COLUMN_NAME,referencedTable:l.REFERENCED_TABLE_NAME,referencedColumn:l.REFERENCED_COLUMN_NAME}));return{name:t,columns:n,foreignKeys:c}}async getTableData(e,t,a){let n=await this.validateTableName(e),r=(t-1)*a,[c]=await this.connection.query(`SELECT * FROM \`${n}\` LIMIT ? OFFSET ?`,[a,r]),[[{total:l}]]=await this.connection.query(`SELECT COUNT(*) as total FROM \`${n}\``),[i]=await this.connection.query(`SHOW COLUMNS FROM \`${n}\``),o=i.map(s=>({name:s.Field,type:s.Type,nullable:s.Null==="YES",key:s.Key,default:s.Default,extra:s.Extra}));return{name:n,columns:o,rows:c,total:l,pagination:{total:l,page:t,limit:a,totalPages:Math.ceil(l/a)}}}async validateTableName(e){if(!(await this.getTables()).includes(e))throw new Error("Invalid table name");return e}async close(){await this.connection.end()}},J=require("mongodb"),ee=100,te=class{constructor(e){this.url=e,p(this,"type","mongodb"),p(this,"client"),p(this,"db")}async connect(){this.client=new J.MongoClient(this.url),await this.client.connect(),this.db=this.client.db()}async getDatabaseName(){return this.db.databaseName}async getTables(){return(await this.db.listCollections().toArray()).map(e=>e.name).sort()}async getTableSchema(e){await this.validateTableName(e);let t=await this.db.collection(e).aggregate([{$sample:{size:ee}}]).toArray(),a=new Map;for(let r of t)for(let[c,l]of Object.entries(r)){let i=a.get(c)||{types:new Set,count:0};i.types.add(ae(l)),i.count++,a.set(c,i)}let n=Array.from(a.entries()).map(([r,{types:c,count:l}])=>({name:r,type:Array.from(c).join(" | "),nullable:l<t.length,key:r==="_id"?"PRI":"",default:null,extra:r==="_id"?"auto_generated":""}));return n.sort((r,c)=>r.name==="_id"?-1:c.name==="_id"?1:r.name.localeCompare(c.name)),{name:e,columns:n,foreignKeys:[]}}async getTableData(e,t,a){await this.validateTableName(e);let n=this.db.collection(e),r=(t-1)*a,[c,l,i]=await Promise.all([n.find().skip(r).limit(a).toArray(),n.countDocuments(),this.getTableSchema(e)]);return{name:e,columns:i.columns,rows:c,total:l,pagination:{total:l,page:t,limit:a,totalPages:Math.ceil(l/a)}}}async validateTableName(e){if(!(await this.getTables()).includes(e))throw new Error("Invalid collection name");return e}async close(){await this.client.close()}};function ae(e){if(e==null)return"Null";if(Array.isArray(e))return"Array";if(e instanceof Date)return"Date";if(typeof e=="object"&&e!==null){let a=e.constructor?.name;return a==="ObjectId"||a==="ObjectID"?"ObjectId":a==="Decimal128"?"Decimal128":a==="Binary"?"Binary":"Object"}let t=typeof e;return t==="string"?"String":t==="number"?Number.isInteger(e)?"Int32":"Double":t==="boolean"?"Boolean":"Unknown"}function S(){return process.env.MONGODB_URL||process.env.MONGODB_URI}function N(){return process.env.MYSQL_URL?"mysql":S()?"mongodb":null}async function ne(){let e=N();if(!e)return null;let t=e==="mysql"?new Z(process.env.MYSQL_URL):new te(S());return await t.connect(),t}var re=async(e,t,a)=>{let n=e.cookies?.studio_token;if(!n)return t.status(401).json({success:!1});if(!O(n))return t.status(401).json({success:!1,message:"Invalid license token"});a()};async function oe({port:e}){H.default.config({path:b.default.join(process.cwd(),".env")});let t=e??5555,a=N(),n=(0,y.default)();n.use((0,$.default)()),n.use(y.default.json({limit:"50mb"})),n.use(y.default.urlencoded({extended:!0,limit:"50mb"})),n.use("/api",(i,o,s)=>{if(i.path==="/validate-license"||i.path==="/status")return s();re(i,o,s)}),n.get("/api/status",(i,o)=>{o.json({configured:!!a,dbType:a})}),n.post("/api/validate-license",async(i,o)=>{let{licenseKey:s}=i.body;if(!s)return o.status(400).json({success:!1});if(s!=="FREE123")return o.status(401).json({success:!1,message:"Invalid license key"});o.cookie("studio_token","FREE123",{httpOnly:!0,sameSite:"lax",secure:!1}),o.json({success:!0})});let r=null;if(a)try{r=await ne(),console.log(`\u{1F50C} Connected to ${a==="mysql"?"MySQL":"MongoDB"}`)}catch(i){throw console.error(`\u274C Failed to connect to ${a}`),i}else console.log("\u26A0\uFE0F No database URL found in .env \u2014 starting in setup mode");let c=(i,o,s)=>{if(!r)return o.status(503).json({success:!1,configured:!1,message:"Database not configured. Add MYSQL_URL or MONGODB_URL to your .env file."});s()};n.get("/api/database",c,async(i,o)=>{try{let s=await r.getDatabaseName();o.json({dbname:s,success:!0,configured:!0})}catch(s){o.json({success:!1,message:"Error fetching db name.",error:String(s)})}}),n.get("/api/tables",c,async(i,o)=>{try{let s=await r.getTables();o.json(s)}catch{o.status(500).json({error:"Failed to fetch tables"})}}),n.get("/api/table/:name/schema",c,async(i,o)=>{try{let s=await r.getTableSchema(i.params.name);o.json(s)}catch(s){console.error(s),o.status(500).json({error:"Failed to fetch table schema"})}}),n.get("/api/table/:name",c,async(i,o)=>{let s=Math.min(parseInt(i.query.limit)||10,1e3),m=Math.max(parseInt(i.query.page)||1,1);try{let u=await r.getTableData(i.params.name,m,s);o.json(u)}catch(u){console.error(u),o.status(500).json({error:"Failed to fetch table data"})}}),n.get("/api/export-pdf",async(i,o)=>{let s=i.cookies?.studio_token;if(!s)return o.status(401).json({error:"Unauthorized"});if(!O(s))return o.status(401).json({error:"Unauthorized"});let m;try{m=await K.default.launch({headless:!0,args:["--no-sandbox","--disable-setuid-sandbox"]});let u=await m.newPage();await u.setCookie({name:"studio_token",value:s,domain:"localhost",path:"/"}),await u.emulateMediaType("print"),await u.addStyleTag({content:`
|
|
23
14
|
@media print {
|
|
24
15
|
html, body {
|
|
25
16
|
height: auto !important;
|
|
@@ -36,5 +27,5 @@ jQIDAQAB
|
|
|
36
27
|
min-height: auto !important;
|
|
37
28
|
}
|
|
38
29
|
}
|
|
39
|
-
`}),await
|
|
40
|
-
\u{1F6D1} Shutting down...`),
|
|
30
|
+
`}),await u.goto(`http://localhost:${t}?view=schema&export=true`,{waitUntil:"networkidle0"});let j=await u.pdf({format:"A4",printBackground:!0,margin:{top:"10mm",bottom:"10mm",left:"10mm",right:"10mm"},preferCSSPageSize:!0});o.contentType("application/pdf"),o.send(j)}catch(u){console.error("PDF generation error:",u),o.status(500).json({error:"Failed to generate PDF"})}finally{m&&await m.close()}});let l=b.default.resolve(__dirname,"spa");return console.log("Serving SPA from:",l),n.use(y.default.static(l)),n.get(/.*/,(i,o)=>{o.sendFile(b.default.join(l,"index.html"))}),new Promise(i=>{let o=n.listen(t,()=>{console.log(`\u{1F680} Database Studio running at http://localhost:${t}`),i(o)}),s=async()=>{console.log(`
|
|
31
|
+
\u{1F6D1} Shutting down...`),o.close(async()=>{try{r&&(await r.close(),console.log("\u{1F50C} Database connection closed"))}catch(m){console.error("Error closing database connection:",m)}finally{process.exit(0)}})};process.once("SIGINT",s),process.once("SIGTERM",s)})}function se(){let e=E.default.networkInterfaces(),t=Object.values(e).flat().find(a=>a&&!a.internal&&a.mac!=="00:00:00:00:00:00")?.mac;return V.default.createHash("sha256").update(t||E.default.hostname()).digest("hex")}function O(e){try{if(e==="FREE123")return{plan:"free"};let t=G.default.verify(e,W,{algorithms:["RS256"],issuer:"database-studio-license",audience:"database-studio-app"}),a=se();return t.machineId!==a?!1:t}catch{return!1}}});var T=h(require("dotenv"),1),D=h(require("path"),1);T.default.config({path:D.default.join(process.cwd(),".env")});async function ie(){try{let e=await Promise.resolve().then(()=>h(M(),1));e.startServer||(console.error("\u274C startServer export not found"),process.exit(1));let t=process.env.PORT||"5555";await e.startServer({port:t});let a=`http://localhost:${t}`,n=(await import("open")).default;await n(a)}catch(e){console.error("\u274C Failed to start Database Studio"),console.error(e),process.exit(1)}}ie();
|
package/dist/server/index.cjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
var
|
|
1
|
+
var j=Object.create;var f=Object.defineProperty;var F=Object.getOwnPropertyDescriptor;var k=Object.getOwnPropertyNames;var U=Object.getPrototypeOf,B=Object.prototype.hasOwnProperty;var x=(a,e,t)=>e in a?f(a,e,{enumerable:!0,configurable:!0,writable:!0,value:t}):a[e]=t;var q=(a,e)=>{for(var t in e)f(a,t,{get:e[t],enumerable:!0})},T=(a,e,t,s)=>{if(e&&typeof e=="object"||typeof e=="function")for(let i of k(e))!B.call(a,i)&&i!==t&&f(a,i,{get:()=>e[i],enumerable:!(s=F(e,i))||s.enumerable});return a};var m=(a,e,t)=>(t=a!=null?j(U(a)):{},T(e||!a||!a.__esModule?f(t,"default",{value:a,enumerable:!0}):t,a)),Y=a=>T(f({},"__esModule",{value:!0}),a);var y=(a,e,t)=>x(a,typeof e!="symbol"?e+"":e,t);var G={};q(G,{startServer:()=>z});module.exports=Y(G);var b=m(require("express"),1),E=m(require("path"),1),R=m(require("dotenv"),1),C=m(require("puppeteer"),1),_=m(require("cookie-parser"),1),L=m(require("jsonwebtoken"),1),w=m(require("os"),1),I=m(require("crypto"),1);var $=process.env.LICENSE_SERVER_URL||"http://localhost:4000",S=`-----BEGIN PUBLIC KEY-----
|
|
2
2
|
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAoNtHPt9FWow0wkX8QszM
|
|
3
3
|
fsS6raS0edOuhkfJYnoMfSHeQ/cBztIV3kTOqu8N2YOzZhPdOFh8b5lBQMyzShXf
|
|
4
4
|
PpvtCjbW0LzkWYiv1ak33RrV9rh2s599toI+sDv/vMv/msB7RHCNEqaIfeeMYdu4
|
|
@@ -7,18 +7,9 @@ Kik1C6Cth/RHAqznVr75VSkiONlX47cFNXny2L4Ujt20wbVyRtErnYdtZDAaVYv7
|
|
|
7
7
|
ZDHKTqt4L2FyD6WwckqHQwDSUbRjWpMyIwJD7YNg2XSKPldtacuw4VlgD0XaAoM5
|
|
8
8
|
jQIDAQAB
|
|
9
9
|
-----END PUBLIC KEY-----
|
|
10
|
-
`;var
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
REFERENCED_TABLE_NAME,
|
|
14
|
-
REFERENCED_COLUMN_NAME
|
|
15
|
-
FROM
|
|
16
|
-
INFORMATION_SCHEMA.KEY_COLUMN_USAGE
|
|
17
|
-
WHERE
|
|
18
|
-
TABLE_NAME = ?
|
|
19
|
-
AND TABLE_SCHEMA = DATABASE()
|
|
20
|
-
AND REFERENCED_TABLE_NAME IS NOT NULL
|
|
21
|
-
`,[t]),d=m.map(l=>({column:l.COLUMN_NAME,referencedTable:l.REFERENCED_TABLE_NAME,referencedColumn:l.REFERENCED_COLUMN_NAME}));e.json({name:t,columns:u,foreignKeys:d})}catch(c){console.error(c),e.status(500).json({error:"Failed to fetch table schema"})}}),i.get("/api/export-pdf",async(o,e)=>{let t=o.cookies?.studio_token;if(!t)return e.status(401).json({error:"Unauthorized"});if(!I(t))return e.status(401).json({error:"Unauthorized"});let u;try{u=await A.default.launch({headless:!0,args:["--no-sandbox","--disable-setuid-sandbox"]});let m=await u.newPage();await m.setCookie({name:"studio_token",value:t,domain:"localhost",path:"/"}),await m.emulateMediaType("print"),await m.addStyleTag({content:`
|
|
10
|
+
`;var v=m(require("mysql2/promise"),1);var g=class{constructor(e){this.url=e;y(this,"type","mysql");y(this,"connection")}async connect(){this.connection=await v.default.createConnection(this.url)}async getDatabaseName(){let[e]=await this.connection.query("SELECT DATABASE()");return e.map(s=>Object.values(s)[0])[0]}async getTables(){let[e]=await this.connection.query("SHOW TABLES");return e.map(t=>Object.values(t)[0])}async getTableSchema(e){let t=await this.validateTableName(e),[s]=await this.connection.query(`SHOW COLUMNS FROM \`${t}\``),i=s.map(n=>({name:n.Field,type:n.Type,nullable:n.Null==="YES",key:n.Key,default:n.Default,extra:n.Extra})),[l]=await this.connection.query(`SELECT COLUMN_NAME, REFERENCED_TABLE_NAME, REFERENCED_COLUMN_NAME
|
|
11
|
+
FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE
|
|
12
|
+
WHERE TABLE_NAME = ? AND TABLE_SCHEMA = DATABASE() AND REFERENCED_TABLE_NAME IS NOT NULL`,[t]),c=l.map(n=>({column:n.COLUMN_NAME,referencedTable:n.REFERENCED_TABLE_NAME,referencedColumn:n.REFERENCED_COLUMN_NAME}));return{name:t,columns:i,foreignKeys:c}}async getTableData(e,t,s){let i=await this.validateTableName(e),l=(t-1)*s,[c]=await this.connection.query(`SELECT * FROM \`${i}\` LIMIT ? OFFSET ?`,[s,l]),[[{total:n}]]=await this.connection.query(`SELECT COUNT(*) as total FROM \`${i}\``),[o]=await this.connection.query(`SHOW COLUMNS FROM \`${i}\``),r=o.map(u=>({name:u.Field,type:u.Type,nullable:u.Null==="YES",key:u.Key,default:u.Default,extra:u.Extra}));return{name:i,columns:r,rows:c,total:n,pagination:{total:n,page:t,limit:s,totalPages:Math.ceil(n/s)}}}async validateTableName(e){if(!(await this.getTables()).includes(e))throw new Error("Invalid table name");return e}async close(){await this.connection.end()}};var M=require("mongodb");var K=100,h=class{constructor(e){this.url=e;y(this,"type","mongodb");y(this,"client");y(this,"db")}async connect(){this.client=new M.MongoClient(this.url),await this.client.connect(),this.db=this.client.db()}async getDatabaseName(){return this.db.databaseName}async getTables(){return(await this.db.listCollections().toArray()).map(t=>t.name).sort()}async getTableSchema(e){await this.validateTableName(e);let s=await this.db.collection(e).aggregate([{$sample:{size:K}}]).toArray(),i=new Map;for(let c of s)for(let[n,o]of Object.entries(c)){let r=i.get(n)||{types:new Set,count:0};r.types.add(H(o)),r.count++,i.set(n,r)}let l=Array.from(i.entries()).map(([c,{types:n,count:o}])=>({name:c,type:Array.from(n).join(" | "),nullable:o<s.length,key:c==="_id"?"PRI":"",default:null,extra:c==="_id"?"auto_generated":""}));return l.sort((c,n)=>c.name==="_id"?-1:n.name==="_id"?1:c.name.localeCompare(n.name)),{name:e,columns:l,foreignKeys:[]}}async getTableData(e,t,s){await this.validateTableName(e);let i=this.db.collection(e),l=(t-1)*s,[c,n,o]=await Promise.all([i.find().skip(l).limit(s).toArray(),i.countDocuments(),this.getTableSchema(e)]);return{name:e,columns:o.columns,rows:c,total:n,pagination:{total:n,page:t,limit:s,totalPages:Math.ceil(n/s)}}}async validateTableName(e){if(!(await this.getTables()).includes(e))throw new Error("Invalid collection name");return e}async close(){await this.client.close()}};function H(a){if(a==null)return"Null";if(Array.isArray(a))return"Array";if(a instanceof Date)return"Date";if(typeof a=="object"&&a!==null){let t=a.constructor?.name;return t==="ObjectId"||t==="ObjectID"?"ObjectId":t==="Decimal128"?"Decimal128":t==="Binary"?"Binary":"Object"}let e=typeof a;return e==="string"?"String":e==="number"?Number.isInteger(a)?"Int32":"Double":e==="boolean"?"Boolean":"Unknown"}function N(){return process.env.MONGODB_URL||process.env.MONGODB_URI}function D(){return process.env.MYSQL_URL?"mysql":N()?"mongodb":null}async function A(){let a=D();if(!a)return null;let e=a==="mysql"?new g(process.env.MYSQL_URL):new h(N());return await e.connect(),e}var Q=async(a,e,t)=>{let s=a.cookies?.studio_token;if(!s)return e.status(401).json({success:!1});if(!O(s))return e.status(401).json({success:!1,message:"Invalid license token"});t()};async function z({port:a}){R.default.config({path:E.default.join(process.cwd(),".env")});let e=a??5555,t=D(),s=(0,b.default)();s.use((0,_.default)()),s.use(b.default.json({limit:"50mb"})),s.use(b.default.urlencoded({extended:!0,limit:"50mb"})),s.use("/api",(n,o,r)=>{if(n.path==="/validate-license"||n.path==="/status")return r();Q(n,o,r)}),s.get("/api/status",(n,o)=>{o.json({configured:!!t,dbType:t})}),s.post("/api/validate-license",async(n,o)=>{let{licenseKey:r}=n.body;if(!r)return o.status(400).json({success:!1});if(r!=="FREE123")return o.status(401).json({success:!1,message:"Invalid license key"});o.cookie("studio_token","FREE123",{httpOnly:!0,sameSite:"lax",secure:!1}),o.json({success:!0})});let i=null;if(t)try{i=await A(),console.log(`\u{1F50C} Connected to ${t==="mysql"?"MySQL":"MongoDB"}`)}catch(n){throw console.error(`\u274C Failed to connect to ${t}`),n}else console.log("\u26A0\uFE0F No database URL found in .env \u2014 starting in setup mode");let l=(n,o,r)=>{if(!i)return o.status(503).json({success:!1,configured:!1,message:"Database not configured. Add MYSQL_URL or MONGODB_URL to your .env file."});r()};s.get("/api/database",l,async(n,o)=>{try{let r=await i.getDatabaseName();o.json({dbname:r,success:!0,configured:!0})}catch(r){o.json({success:!1,message:"Error fetching db name.",error:String(r)})}}),s.get("/api/tables",l,async(n,o)=>{try{let r=await i.getTables();o.json(r)}catch{o.status(500).json({error:"Failed to fetch tables"})}}),s.get("/api/table/:name/schema",l,async(n,o)=>{try{let r=await i.getTableSchema(n.params.name);o.json(r)}catch(r){console.error(r),o.status(500).json({error:"Failed to fetch table schema"})}}),s.get("/api/table/:name",l,async(n,o)=>{let r=Math.min(parseInt(n.query.limit)||10,1e3),u=Math.max(parseInt(n.query.page)||1,1);try{let p=await i.getTableData(n.params.name,u,r);o.json(p)}catch(p){console.error(p),o.status(500).json({error:"Failed to fetch table data"})}}),s.get("/api/export-pdf",async(n,o)=>{let r=n.cookies?.studio_token;if(!r)return o.status(401).json({error:"Unauthorized"});if(!O(r))return o.status(401).json({error:"Unauthorized"});let p;try{p=await C.default.launch({headless:!0,args:["--no-sandbox","--disable-setuid-sandbox"]});let d=await p.newPage();await d.setCookie({name:"studio_token",value:r,domain:"localhost",path:"/"}),await d.emulateMediaType("print"),await d.addStyleTag({content:`
|
|
22
13
|
@media print {
|
|
23
14
|
html, body {
|
|
24
15
|
height: auto !important;
|
|
@@ -35,5 +26,5 @@ jQIDAQAB
|
|
|
35
26
|
min-height: auto !important;
|
|
36
27
|
}
|
|
37
28
|
}
|
|
38
|
-
`}),await
|
|
39
|
-
\u{1F6D1} Shutting down...`),
|
|
29
|
+
`}),await d.goto(`http://localhost:${e}?view=schema&export=true`,{waitUntil:"networkidle0"});let P=await d.pdf({format:"A4",printBackground:!0,margin:{top:"10mm",bottom:"10mm",left:"10mm",right:"10mm"},preferCSSPageSize:!0});o.contentType("application/pdf"),o.send(P)}catch(d){console.error("PDF generation error:",d),o.status(500).json({error:"Failed to generate PDF"})}finally{p&&await p.close()}});let c=E.default.resolve(__dirname,"spa");return console.log("Serving SPA from:",c),s.use(b.default.static(c)),s.get(/.*/,(n,o)=>{o.sendFile(E.default.join(c,"index.html"))}),new Promise(n=>{let o=s.listen(e,()=>{console.log(`\u{1F680} Database Studio running at http://localhost:${e}`),n(o)}),r=async()=>{console.log(`
|
|
30
|
+
\u{1F6D1} Shutting down...`),o.close(async()=>{try{i&&(await i.close(),console.log("\u{1F50C} Database connection closed"))}catch(u){console.error("Error closing database connection:",u)}finally{process.exit(0)}})};process.once("SIGINT",r),process.once("SIGTERM",r)})}function V(){let a=w.default.networkInterfaces(),e=Object.values(a).flat().find(t=>t&&!t.internal&&t.mac!=="00:00:00:00:00:00")?.mac;return I.default.createHash("sha256").update(e||w.default.hostname()).digest("hex")}function O(a){try{if(a==="FREE123")return{plan:"free"};let e=L.default.verify(a,S,{algorithms:["RS256"],issuer:"database-studio-license",audience:"database-studio-app"}),t=V();return e.machineId!==t?!1:e}catch{return!1}}0&&(module.exports={startServer});
|