database-studio 1.0.6 → 1.0.8

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 CHANGED
@@ -1,61 +1,82 @@
1
- # Database Studio App 🚀
1
+ # Database Studio 🚀
2
2
 
3
- [![Version](https://img.shields.io/badge/version-1.0.1-blue.svg)](./package.json)
4
- [![License](https://img.shields.io/badge/license-private-red.svg)](#license)
5
- [![Tech Stack](https://img.shields.io/badge/tech-React%20%7C%20Express%20%7C%20MySQL-orange.svg)](#tech-stack)
3
+ [![Version](https://img.shields.io/badge/version-1.0.6-blue.svg)](./package.json)
4
+ [![License](https://img.shields.io/badge/license-MIT-green.svg)](#license)
5
+ [![Tech Stack](https://img.shields.io/badge/tech-React%20%7C%20Express%20%7C%20MySQL%20%7C%20MongoDB-orange.svg)](#tech-stack)
6
6
 
7
- **Database Studio App** is a high-performance, full-stack database management and visualization tool. Designed for developers and database administrators, it provides a modern, intuitive interface to explore, analyze, and document your MySQL databases with ease.
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 Database Viewer
14
- Explore your data with a high-speed, paginated data table. View real-time records from your MySQL tables with a clean, responsive interface.
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. Our grid-based **Schema View** transforms complex table relationships into beautiful, interactive cards with dynamic color-coding.
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
- ### 📄 Professional PDF Documentation
20
- Generate high-fidelity database schema reports with a single click. Powered by **Puppeteer** server-side rendering, our exports ensure 100% style accuracy and a professional A4 print layout.
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
- ### 🔐 Enterprise-Grade Security
23
- - **Machine-ID Binding**: Licenses are securely tied to unique hardware identifiers.
24
- - **JWT Authentication**: Secure session management using JSON Web Tokens and HTTP-only cookies.
25
- - **RSA-256 Verification**: Offline license verification using public/private key pairs.
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 Experience
28
- - **Customizable Layout**: Toggle between 1-4 column grid views.
29
- - **View Switching**: Seamlessly jump between raw data and architectural schemas.
30
- - **Fast Search**: Quickly find the tables and columns you need.
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**: v18 or higher
38
- - **pnpm**: Recommended package manager
39
- - **MySQL**: Access to a running MySQL instance
44
+ - **Node.js** v18+
45
+ - **MySQL** or **MongoDB** instance
40
46
 
41
- ### Installation
47
+ ### Usage via npx
42
48
 
43
49
  ```bash
44
- # Install dependencies
45
- pnpm install
50
+ npx database-studio
51
+ ```
52
+
53
+ ### Usage via global install
46
54
 
47
- # Build the application
48
- pnpm build
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 the root of the `studio-app` directory:
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
- - **Database**: [MySQL](https://www.mysql.com/)
67
- - **Build System**: [Vite](https://vitejs.dev/), [tsup](https://tsup.egoist.dev/)
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
- ## 🏗️ Project Architecture
92
+ ## 🏗️ Architecture
72
93
 
73
94
  ```text
74
- client/ # Modern React SPA
75
- ├── components/ # Reusable UI & Logic components
76
- ├── pages/ # Route-level components
77
- └── lib/ # Utilities and API wrappers
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
- server/ # Express API & Documentation Engine
80
- ├── index.ts # Main entry point with License & PDF logic
81
- └── config.ts # Server configuration
112
+ ### Driver Pattern
82
113
 
83
- bin/ # CLI tools for global linking
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
- Our export engine doesn't just "print" the page. It uses a sophisticated server-side flow:
91
- 1. **Headless Navigation**: Puppeteer visits a specialized `?export=true` route.
92
- 2. **Adaptive CSS**: The app re-renders specifically for print, enforcing a single-column layout and hiding interactive elements.
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
- This application requires a valid license key for operation. The licensing system uses **RSA-256** signatures to ensure integrity and **Machine-ID** hashing to prevent unauthorized distribution.
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 App 🚀
1
+ # Database Studio 🚀
2
2
 
3
- [![Version](https://img.shields.io/badge/version-1.0.1-blue.svg)](./package.json)
4
- [![License](https://img.shields.io/badge/license-private-red.svg)](#license)
5
- [![Tech Stack](https://img.shields.io/badge/tech-React%20%7C%20Express%20%7C%20MySQL-orange.svg)](#tech-stack)
3
+ [![Version](https://img.shields.io/badge/version-1.0.6-blue.svg)](./package.json)
4
+ [![License](https://img.shields.io/badge/license-MIT-green.svg)](#license)
5
+ [![Tech Stack](https://img.shields.io/badge/tech-React%20%7C%20Express%20%7C%20MySQL%20%7C%20MongoDB-orange.svg)](#tech-stack)
6
6
 
7
- **Database Studio App** is a high-performance, full-stack database management and visualization tool. Designed for developers and database administrators, it provides a modern, intuitive interface to explore, analyze, and document your MySQL databases with ease.
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 Database Viewer
14
- Explore your data with a high-speed, paginated data table. View real-time records from your MySQL tables with a clean, responsive interface.
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. Our grid-based **Schema View** transforms complex table relationships into beautiful, interactive cards with dynamic color-coding.
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
- ### 📄 Professional PDF Documentation
20
- Generate high-fidelity database schema reports with a single click. Powered by **Puppeteer** server-side rendering, our exports ensure 100% style accuracy and a professional A4 print layout.
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
- ### 🔐 Enterprise-Grade Security
23
- - **Machine-ID Binding**: Licenses are securely tied to unique hardware identifiers.
24
- - **JWT Authentication**: Secure session management using JSON Web Tokens and HTTP-only cookies.
25
- - **RSA-256 Verification**: Offline license verification using public/private key pairs.
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 Experience
28
- - **Customizable Layout**: Toggle between 1-4 column grid views.
29
- - **View Switching**: Seamlessly jump between raw data and architectural schemas.
30
- - **Fast Search**: Quickly find the tables and columns you need.
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**: v18 or higher
38
- - **pnpm**: Recommended package manager
39
- - **MySQL**: Access to a running MySQL instance
44
+ - **Node.js** v18+
45
+ - **MySQL** or **MongoDB** instance
40
46
 
41
- ### Installation
47
+ ### Usage via npx
42
48
 
43
49
  ```bash
44
- # Install dependencies
45
- pnpm install
50
+ npx database-studio
51
+ ```
52
+
53
+ ### Usage via global install
46
54
 
47
- # Build the application
48
- pnpm build
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 the root of the `studio-app` directory:
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
- - **Database**: [MySQL](https://www.mysql.com/)
67
- - **Build System**: [Vite](https://vitejs.dev/), [tsup](https://tsup.egoist.dev/)
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
- ## 🏗️ Project Architecture
92
+ ## 🏗️ Architecture
72
93
 
73
94
  ```text
74
- client/ # Modern React SPA
75
- ├── components/ # Reusable UI & Logic components
76
- ├── pages/ # Route-level components
77
- └── lib/ # Utilities and API wrappers
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
- server/ # Express API & Documentation Engine
80
- ├── index.ts # Main entry point with License & PDF logic
81
- └── config.ts # Server configuration
112
+ ### Driver Pattern
82
113
 
83
- bin/ # CLI tools for global linking
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
- Our export engine doesn't just "print" the page. It uses a sophisticated server-side flow:
91
- 1. **Headless Navigation**: Puppeteer visits a specialized `?export=true` route.
92
- 2. **Adaptive CSS**: The app re-renders specifically for print, enforcing a single-column layout and hiding interactive elements.
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
- This application requires a valid license key for operation. The licensing system uses **RSA-256** signatures to ensure integrity and **Machine-ID** hashing to prevent unauthorized distribution.
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 D=Object.create;var w=Object.defineProperty;var I=Object.getOwnPropertyDescriptor;var T=Object.getOwnPropertyNames;var q=Object.getPrototypeOf,k=Object.prototype.hasOwnProperty;var B=(e,t)=>()=>(t||e((t={exports:{}}).exports,t),t.exports);var U=(e,t,r,n)=>{if(t&&typeof t=="object"||typeof t=="function")for(let s of T(t))!k.call(e,s)&&s!==r&&w(e,s,{get:()=>t[s],enumerable:!(n=I(t,s))||n.enumerable});return e};var y=(e,t,r)=>(r=e!=null?D(q(e)):{},U(t||!e||!e.__esModule?w(r,"default",{value:e,enumerable:!0}):r,e));var C=B((se,N)=>{var P=Object.create,g=Object.defineProperty,x=Object.getOwnPropertyDescriptor,Q=Object.getOwnPropertyNames,Y=Object.getPrototypeOf,H=Object.prototype.hasOwnProperty,z=(e,t)=>{for(var r in t)g(e,r,{get:t[r],enumerable:!0})},A=(e,t,r,n)=>{if(t&&typeof t=="object"||typeof t=="function")for(let s of Q(t))!H.call(e,s)&&s!==r&&g(e,s,{get:()=>t[s],enumerable:!(n=x(t,s))||n.enumerable});return e},d=(e,t,r)=>(r=e!=null?P(Y(e)):{},A(t||!e||!e.__esModule?g(r,"default",{value:e,enumerable:!0}):r,e)),K=e=>A(g({},"__esModule",{value:!0}),e),O={};z(O,{startServer:()=>te});N.exports=K(O);var h=d(require("express"),1),v=d(require("path"),1),W=d(require("dotenv"),1),V=d(require("mysql2/promise"),1),X=d(require("puppeteer"),1),$=d(require("cookie-parser"),1),G=d(require("jsonwebtoken"),1),b=d(require("os"),1),Z=d(require("crypto"),1),ne=process.env.LICENSE_SERVER_URL||"http://localhost:4000",J=`-----BEGIN PUBLIC KEY-----
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
- `,ee=async(e,t,r)=>{let n=e.cookies?.studio_token;if(!n)return t.status(401).json({success:!1});if(!L(n))return t.status(401).json({success:!1,message:"Invalid license token"});r()};async function te({port:e}){W.default.config({path:v.default.join(process.cwd(),".env")});let t=e??5555,r=!!process.env.MYSQL_URL,n=(0,h.default)();n.use((0,$.default)()),n.use(h.default.json({limit:"50mb"})),n.use(h.default.urlencoded({extended:!0,limit:"50mb"})),n.use("/api",(i,a,o)=>{if(i.path==="/validate-license"||i.path==="/status")return o();ee(i,a,o)}),n.get("/api/status",(i,a)=>{a.json({configured:r})}),n.post("/api/validate-license",async(i,a)=>{let{licenseKey:o}=i.body;if(!o)return a.status(400).json({success:!1});if(o!=="FREE123")return a.status(401).json({success:!1,message:"Invalid license key"});a.cookie("studio_token","FREE123",{httpOnly:!0,sameSite:"lax",secure:!1}),a.json({success:!0})});let s=null;if(r)try{s=await V.default.createConnection(process.env.MYSQL_URL),console.log("\u{1F50C} Connected to MySQL")}catch(i){throw console.error("\u274C Failed to connect to MySQL"),i}else console.log("\u26A0\uFE0F MYSQL_URL not found in .env \u2014 starting in setup mode");let E=(i,a,o)=>{if(!s)return a.status(503).json({success:!1,configured:!1,message:"Database not configured. Add MYSQL_URL to your .env file."});o()};n.get("/api/database",E,async(i,a)=>{try{let[o]=await s.query("SELECT DATABASE()"),l=o.map(c=>Object.values(c)[0]);a.json({dbname:l[0],success:!0,configured:!0,message:"DB fetched successfully"})}catch(o){a.json({success:!1,message:"Error fetching db name.",error:String(o)})}}),n.get("/api/tables",E,async(i,a)=>{let[o]=await s.query("SHOW TABLES"),l=o.map(c=>Object.values(c)[0]);a.json(l)}),n.get("/api/table/:name/schema",E,async(i,a)=>{let o=await M(i.params.name,s);try{let[l]=await s.query(`SHOW COLUMNS FROM \`${o}\``),c=l.map(u=>({name:u.Field,type:u.Type,nullable:u.Null==="YES",key:u.Key,default:u.Default,extra:u.Extra})),[m]=await s.query(`
12
- SELECT
13
- COLUMN_NAME,
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 c.goto(`http://localhost:${t}?view=schema&export=true`,{waitUntil:"networkidle0"});let m=await c.pdf({format:"A4",printBackground:!0,margin:{top:"10mm",bottom:"10mm",left:"10mm",right:"10mm"},preferCSSPageSize:!0});a.contentType("application/pdf"),a.send(m)}catch(c){console.error("PDF generation error:",c),a.status(500).json({error:"Failed to generate PDF"})}finally{l&&await l.close()}}),n.get("/api/table/:name",E,async(i,a)=>{let o=await M(i.params.name,s),l=Math.min(parseInt(i.query.limit)||10,1e3),c=Math.max(parseInt(i.query.page)||1,1),m=(c-1)*l;try{let[f]=await s.query(`SELECT * FROM \`${o}\` LIMIT ? OFFSET ?`,[l,m]),[[{total:u}]]=await s.query(`SELECT COUNT(*) as total FROM \`${o}\``),[j]=await s.query(`SHOW COLUMNS FROM \`${o}\``),_=j.map(p=>({name:p.Field,type:p.Type,nullable:p.Null==="YES",key:p.Key,default:p.Default,extra:p.Extra}));a.json({name:o,columns:_,rows:f,total:u,pagination:{total:u,page:c,limit:l,totalPages:Math.ceil(u/l)}})}catch(f){console.error(f),a.status(500).json({error:"Failed to fetch table"})}});let S=v.default.resolve(__dirname,"spa");return console.log("Serving SPA from:",S),n.use(h.default.static(S)),n.get(/.*/,(i,a)=>{a.sendFile(v.default.join(S,"index.html"))}),new Promise(i=>{let a=n.listen(t,()=>{console.log(`\u{1F680} Database Studio running at http://localhost:${t}`),i(a)}),o=async()=>{console.log(`
40
- \u{1F6D1} Shutting down...`),a.close(async()=>{try{s&&(await s.end(),console.log("\u{1F50C} MySQL connection closed"))}catch(l){console.error("Error closing MySQL connection:",l)}finally{process.exit(0)}})};process.once("SIGINT",o),process.once("SIGTERM",o)})}async function M(e,t){let[r]=await t.query("SHOW TABLES");if(!r.map(n=>Object.values(n)[0]).includes(e))throw new Error("Invalid table name");return e}function ae(){let e=b.default.networkInterfaces(),t=Object.values(e).flat().find(r=>r&&!r.internal&&r.mac!=="00:00:00:00:00:00")?.mac;return Z.default.createHash("sha256").update(t||b.default.hostname()).digest("hex")}function L(e){try{if(e==="FREE123")return{plan:"free"};let t=G.default.verify(e,J,{algorithms:["RS256"],issuer:"database-studio-license",audience:"database-studio-app"}),r=ae();return t.machineId!==r?!1:t}catch{return!1}}});var F=y(require("dotenv"),1),R=y(require("path"),1);F.default.config({path:R.default.join(process.cwd(),".env")});async function re(){try{let e=await Promise.resolve().then(()=>y(C(),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 r=`http://localhost:${t}`,n=(await import("open")).default;await n(r)}catch(e){console.error("\u274C Failed to start Database Studio"),console.error(e),process.exit(1)}}re();
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.STUDIO_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();
@@ -1,4 +1,4 @@
1
- var F=Object.create;var h=Object.defineProperty;var T=Object.getOwnPropertyDescriptor;var j=Object.getOwnPropertyNames;var U=Object.getPrototypeOf,k=Object.prototype.hasOwnProperty;var B=(a,s)=>{for(var r in s)h(a,r,{get:s[r],enumerable:!0})},M=(a,s,r,i)=>{if(s&&typeof s=="object"||typeof s=="function")for(let n of j(s))!k.call(a,n)&&n!==r&&h(a,n,{get:()=>s[n],enumerable:!(i=T(s,n))||i.enumerable});return a};var p=(a,s,r)=>(r=a!=null?F(U(a)):{},M(s||!a||!a.__esModule?h(r,"default",{value:a,enumerable:!0}):r,a)),x=a=>M(h({},"__esModule",{value:!0}),a);var Q={};B(Q,{startServer:()=>Y});module.exports=x(Q);var f=p(require("express"),1),g=p(require("path"),1),b=p(require("dotenv"),1),N=p(require("mysql2/promise"),1),A=p(require("puppeteer"),1),C=p(require("cookie-parser"),1),_=p(require("jsonwebtoken"),1),w=p(require("os"),1),v=p(require("crypto"),1);var K=process.env.LICENSE_SERVER_URL||"http://localhost:4000",L=`-----BEGIN PUBLIC KEY-----
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 P=async(a,s,r)=>{let i=a.cookies?.studio_token;if(!i)return s.status(401).json({success:!1});if(!I(i))return s.status(401).json({success:!1,message:"Invalid license token"});r()};async function Y({port:a}){b.default.config({path:g.default.join(process.cwd(),".env")});let s=a??5555,r=!!process.env.MYSQL_URL,i=(0,f.default)();i.use((0,C.default)()),i.use(f.default.json({limit:"50mb"})),i.use(f.default.urlencoded({extended:!0,limit:"50mb"})),i.use("/api",(o,e,t)=>{if(o.path==="/validate-license"||o.path==="/status")return t();P(o,e,t)}),i.get("/api/status",(o,e)=>{e.json({configured:r})}),i.post("/api/validate-license",async(o,e)=>{let{licenseKey:t}=o.body;if(!t)return e.status(400).json({success:!1});if(t!=="FREE123")return e.status(401).json({success:!1,message:"Invalid license key"});e.cookie("studio_token","FREE123",{httpOnly:!0,sameSite:"lax",secure:!1}),e.json({success:!0})});let n=null;if(r)try{n=await N.default.createConnection(process.env.MYSQL_URL),console.log("\u{1F50C} Connected to MySQL")}catch(o){throw console.error("\u274C Failed to connect to MySQL"),o}else console.log("\u26A0\uFE0F MYSQL_URL not found in .env \u2014 starting in setup mode");let y=(o,e,t)=>{if(!n)return e.status(503).json({success:!1,configured:!1,message:"Database not configured. Add MYSQL_URL to your .env file."});t()};i.get("/api/database",y,async(o,e)=>{try{let[t]=await n.query("SELECT DATABASE()"),c=t.map(u=>Object.values(u)[0]);e.json({dbname:c[0],success:!0,configured:!0,message:"DB fetched successfully"})}catch(t){e.json({success:!1,message:"Error fetching db name.",error:String(t)})}}),i.get("/api/tables",y,async(o,e)=>{let[t]=await n.query("SHOW TABLES"),c=t.map(u=>Object.values(u)[0]);e.json(c)}),i.get("/api/table/:name/schema",y,async(o,e)=>{let t=await R(o.params.name,n);try{let[c]=await n.query(`SHOW COLUMNS FROM \`${t}\``),u=c.map(l=>({name:l.Field,type:l.Type,nullable:l.Null==="YES",key:l.Key,default:l.Default,extra:l.Extra})),[m]=await n.query(`
11
- SELECT
12
- COLUMN_NAME,
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 m.goto(`http://localhost:${s}?view=schema&export=true`,{waitUntil:"networkidle0"});let d=await m.pdf({format:"A4",printBackground:!0,margin:{top:"10mm",bottom:"10mm",left:"10mm",right:"10mm"},preferCSSPageSize:!0});e.contentType("application/pdf"),e.send(d)}catch(m){console.error("PDF generation error:",m),e.status(500).json({error:"Failed to generate PDF"})}finally{u&&await u.close()}}),i.get("/api/table/:name",y,async(o,e)=>{let t=await R(o.params.name,n),c=Math.min(parseInt(o.query.limit)||10,1e3),u=Math.max(parseInt(o.query.page)||1,1),m=(u-1)*c;try{let[d]=await n.query(`SELECT * FROM \`${t}\` LIMIT ? OFFSET ?`,[c,m]),[[{total:l}]]=await n.query(`SELECT COUNT(*) as total FROM \`${t}\``),[O]=await n.query(`SHOW COLUMNS FROM \`${t}\``),D=O.map(E=>({name:E.Field,type:E.Type,nullable:E.Null==="YES",key:E.Key,default:E.Default,extra:E.Extra}));e.json({name:t,columns:D,rows:d,total:l,pagination:{total:l,page:u,limit:c,totalPages:Math.ceil(l/c)}})}catch(d){console.error(d),e.status(500).json({error:"Failed to fetch table"})}});let S=g.default.resolve(__dirname,"spa");return console.log("Serving SPA from:",S),i.use(f.default.static(S)),i.get(/.*/,(o,e)=>{e.sendFile(g.default.join(S,"index.html"))}),new Promise(o=>{let e=i.listen(s,()=>{console.log(`\u{1F680} Database Studio running at http://localhost:${s}`),o(e)}),t=async()=>{console.log(`
39
- \u{1F6D1} Shutting down...`),e.close(async()=>{try{n&&(await n.end(),console.log("\u{1F50C} MySQL connection closed"))}catch(c){console.error("Error closing MySQL connection:",c)}finally{process.exit(0)}})};process.once("SIGINT",t),process.once("SIGTERM",t)})}async function R(a,s){let[r]=await s.query("SHOW TABLES");if(!r.map(n=>Object.values(n)[0]).includes(a))throw new Error("Invalid table name");return a}function q(){let a=w.default.networkInterfaces(),s=Object.values(a).flat().find(r=>r&&!r.internal&&r.mac!=="00:00:00:00:00:00")?.mac;return v.default.createHash("sha256").update(s||w.default.hostname()).digest("hex")}function I(a){try{if(a==="FREE123")return{plan:"free"};let s=_.default.verify(a,L,{algorithms:["RS256"],issuer:"database-studio-license",audience:"database-studio-app"}),r=q();return s.machineId!==r?!1:s}catch{return!1}}0&&(module.exports={startServer});
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});