sql-dashboard 1.0.0 → 1.0.1
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/dist/chunk-ABIHDUB7.mjs +76 -0
- package/dist/{chunk-TNHUK2FI.mjs → chunk-RU3R3RGI.mjs} +203 -5
- package/dist/demo/index.d.mts +2 -0
- package/dist/demo/index.d.ts +2 -0
- package/dist/demo/index.js +91360 -0
- package/dist/demo/index.mjs +339 -0
- package/dist/drivers/mysql.driver.mjs +2 -2
- package/dist/export/index.mjs +7 -67
- package/dist/index.js +3 -3
- package/dist/index.mjs +3 -3
- package/dist/middleware/express.js +3 -3
- package/dist/middleware/express.mjs +3 -3
- package/dist/middleware/fastify.js +3 -3
- package/dist/middleware/fastify.mjs +3 -3
- package/package.json +8 -2
- package/dist/{chunk-7YLO3OSN.mjs → chunk-62XBCHG5.mjs} +3 -3
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
import {
|
|
2
|
+
toCSV,
|
|
3
|
+
toJSON,
|
|
4
|
+
toJSONArray,
|
|
5
|
+
toJSONLines
|
|
6
|
+
} from "../chunk-ABIHDUB7.mjs";
|
|
7
|
+
import {
|
|
8
|
+
Logger,
|
|
9
|
+
RateLimiter,
|
|
10
|
+
ReadOnlyGuard,
|
|
11
|
+
SQLDashboard,
|
|
12
|
+
calculatePagination,
|
|
13
|
+
createDashboard,
|
|
14
|
+
createPaginatedResult,
|
|
15
|
+
detectInjection,
|
|
16
|
+
detectStatementType,
|
|
17
|
+
formatResultRow,
|
|
18
|
+
formatSQL,
|
|
19
|
+
sanitizeIdentifier,
|
|
20
|
+
validateQuery
|
|
21
|
+
} from "../chunk-RU3R3RGI.mjs";
|
|
22
|
+
import "../chunk-MTCZXLV5.mjs";
|
|
23
|
+
import "../chunk-62XBCHG5.mjs";
|
|
24
|
+
import "../chunk-HIQUIRDJ.mjs";
|
|
25
|
+
import "../chunk-YGKUVVJT.mjs";
|
|
26
|
+
import {
|
|
27
|
+
QueryTimer
|
|
28
|
+
} from "../chunk-P4QE6SGC.mjs";
|
|
29
|
+
import "../chunk-OCL5Y3AH.mjs";
|
|
30
|
+
|
|
31
|
+
// src/demo/features/setup.ts
|
|
32
|
+
async function demoBasicSetup() {
|
|
33
|
+
const options = {
|
|
34
|
+
driver: { type: "sqlite" /* SQLITE */, connection: { mode: "memory" } },
|
|
35
|
+
logger: { level: "silent" },
|
|
36
|
+
autoConnect: false,
|
|
37
|
+
security: { requireWhere: true, bannedStatements: ["TRUNCATE"], maxRows: 100, queryTimeout: 5e3 }
|
|
38
|
+
};
|
|
39
|
+
const db = new SQLDashboard(options);
|
|
40
|
+
await db.connect();
|
|
41
|
+
const status = await db.status();
|
|
42
|
+
console.log(" [OK] Connected to SQLite database");
|
|
43
|
+
console.log(` Dashboard Version: ${status.version}`);
|
|
44
|
+
console.log(` Driver: ${status.driver}`);
|
|
45
|
+
console.log(` Connected: ${status.connected}`);
|
|
46
|
+
return db;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// src/demo/features/tables.ts
|
|
50
|
+
async function demoTableCreation(db) {
|
|
51
|
+
await db.query(`CREATE TABLE users (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, email TEXT UNIQUE NOT NULL, role TEXT DEFAULT 'user', active INTEGER DEFAULT 1, created_at DATETIME DEFAULT CURRENT_TIMESTAMP)`);
|
|
52
|
+
await db.query(`CREATE TABLE posts (id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER NOT NULL, title TEXT NOT NULL, body TEXT, published INTEGER DEFAULT 0, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (user_id) REFERENCES users(id))`);
|
|
53
|
+
await db.query(`CREATE TABLE comments (id INTEGER PRIMARY KEY AUTOINCREMENT, post_id INTEGER NOT NULL, user_id INTEGER NOT NULL, content TEXT NOT NULL, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (post_id) REFERENCES posts(id), FOREIGN KEY (user_id) REFERENCES users(id))`);
|
|
54
|
+
const users = [
|
|
55
|
+
{ name: "Alice Johnson", email: "alice@example.com", role: "admin" },
|
|
56
|
+
{ name: "Bob Smith", email: "bob@example.com", role: "editor" },
|
|
57
|
+
{ name: "Charlie Brown", email: "charlie@example.com", role: "user" },
|
|
58
|
+
{ name: "Diana Prince", email: "diana@example.com", role: "user" },
|
|
59
|
+
{ name: "Eve Wilson", email: "eve@example.com", role: "editor" }
|
|
60
|
+
];
|
|
61
|
+
for (const u of users) {
|
|
62
|
+
await db.query("INSERT INTO users (name, email, role) VALUES (?, ?, ?)", { params: [u.name, u.email, u.role] });
|
|
63
|
+
}
|
|
64
|
+
await db.query("INSERT INTO posts (user_id, title, body) VALUES (1, 'Getting Started with SQL', 'SQL is powerful...')");
|
|
65
|
+
await db.query("INSERT INTO posts (user_id, title, body) VALUES (1, 'Advanced Queries', 'Joins and subqueries...')");
|
|
66
|
+
await db.query("INSERT INTO posts (user_id, title, body) VALUES (2, 'Database Design Tips', 'Normalization...')");
|
|
67
|
+
await db.query("INSERT INTO posts (user_id, title, body, published) VALUES (2, 'Performance Tuning', 'Optimize queries...', 1)");
|
|
68
|
+
await db.query("INSERT INTO posts (user_id, title, body, published) VALUES (3, 'Hello World', 'My first post!', 1)");
|
|
69
|
+
await db.query("INSERT INTO comments (post_id, user_id, content) VALUES (1, 2, 'Great article!')");
|
|
70
|
+
await db.query("INSERT INTO comments (post_id, user_id, content) VALUES (1, 3, 'Very helpful!')");
|
|
71
|
+
await db.query("INSERT INTO comments (post_id, user_id, content) VALUES (2, 4, 'Explain joins more?')");
|
|
72
|
+
await db.query("INSERT INTO comments (post_id, user_id, content) VALUES (3, 1, 'Nice tips!')");
|
|
73
|
+
console.log(" [OK] 3 tables created with sample data");
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// src/demo/features/queries.ts
|
|
77
|
+
async function demoQueries(db) {
|
|
78
|
+
const r1 = await db.query("SELECT id, name, email, role FROM users");
|
|
79
|
+
console.log(` Simple SELECT: ${r1.rowCount} rows in ${r1.duration.toFixed(2)}ms`);
|
|
80
|
+
const r2 = await db.query("SELECT id, name, email FROM users WHERE role = ?", { params: ["editor"] });
|
|
81
|
+
console.log(` Parameterized: ${r2.rowCount} editors found`);
|
|
82
|
+
const r3 = await db.query(`SELECT p.id, p.title, u.name as author FROM posts p JOIN users u ON u.id = p.user_id ORDER BY p.created_at DESC`);
|
|
83
|
+
console.log(` JOIN: ${r3.rowCount} posts with authors`);
|
|
84
|
+
const r4 = await db.query(`SELECT u.role, COUNT(DISTINCT u.id) as users, COUNT(p.id) as posts FROM users u LEFT JOIN posts p ON p.user_id = u.id GROUP BY u.role`);
|
|
85
|
+
for (const row of r4.rows) {
|
|
86
|
+
console.log(` GROUP BY: role=${row.role}, users=${row.users}, posts=${row.posts}`);
|
|
87
|
+
}
|
|
88
|
+
const r5 = await db.query("SELECT * FROM users", { maxRows: 2 });
|
|
89
|
+
console.log(` maxRows=2: ${r5.rowCount} rows (${r5.warning ? "truncated" : "full"})`);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// src/demo/features/transactions.ts
|
|
93
|
+
async function demoTransactions(db) {
|
|
94
|
+
const result = await db.transaction(async (query) => {
|
|
95
|
+
await query("INSERT INTO users (name, email, role) VALUES ('Tx User', 'tx@test.com', 'user')");
|
|
96
|
+
const count = await query("SELECT COUNT(*) as c FROM users");
|
|
97
|
+
return `User added, total ${JSON.stringify(count.rows[0].c)}`;
|
|
98
|
+
});
|
|
99
|
+
console.log(` Transaction OK: ${result}`);
|
|
100
|
+
try {
|
|
101
|
+
await db.transaction(async (query) => {
|
|
102
|
+
await query("INSERT INTO users (name, email, role) VALUES ('Rollback', 'rb@test.com', 'user')");
|
|
103
|
+
await query("INSERT INTO nonexistent VALUES (1)");
|
|
104
|
+
});
|
|
105
|
+
} catch {
|
|
106
|
+
const check = await db.query("SELECT COUNT(*) as c FROM users WHERE email = 'rb@test.com'");
|
|
107
|
+
console.log(` Rollback verified: user present = ${check.rows[0].c === 0}`);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
async function demoBatchQueries(db) {
|
|
111
|
+
const results = await db.batch([
|
|
112
|
+
"SELECT COUNT(*) as u FROM users",
|
|
113
|
+
"SELECT COUNT(*) as p FROM posts",
|
|
114
|
+
"SELECT COUNT(*) as c FROM comments"
|
|
115
|
+
]);
|
|
116
|
+
for (const r of results) {
|
|
117
|
+
console.log(` Batch: ${JSON.stringify(r.rows[0])} (${r.duration.toFixed(1)}ms)`);
|
|
118
|
+
}
|
|
119
|
+
const multi = await db.executeBatch("SELECT 1 as a; SELECT 2 as b;");
|
|
120
|
+
console.log(` executeBatch: ${multi.length} queries`);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// src/demo/features/schema.ts
|
|
124
|
+
async function demoSchema(db) {
|
|
125
|
+
const tables = await db.schema.getTables();
|
|
126
|
+
for (const t of tables) {
|
|
127
|
+
console.log(` Table: ${t.name} (${t.columns.length} cols, ${t.rowCount} rows)`);
|
|
128
|
+
for (const c of t.columns) {
|
|
129
|
+
const meta = `${c.primaryKey ? "PK " : ""}${c.nullable ? "" : "NOT NULL "}${c.defaultValue ? `DEFAULT ${c.defaultValue}` : ""}`;
|
|
130
|
+
console.log(` ${c.name}: ${c.type} ${meta}`);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
const indexes = await db.schema.getIndexes("users");
|
|
134
|
+
for (const idx of indexes) {
|
|
135
|
+
console.log(` Index: ${idx.name} on ${idx.columns.join(", ")} (${idx.type})`);
|
|
136
|
+
}
|
|
137
|
+
const fks = await db.schema.getForeignKeys("comments");
|
|
138
|
+
for (const fk of fks) {
|
|
139
|
+
console.log(` FK: ${fk.column} -> ${fk.referencedTable}(${fk.referencedColumn})`);
|
|
140
|
+
}
|
|
141
|
+
const summary = await db.schema.getTableSummary("users");
|
|
142
|
+
console.log(` Summary: ${summary.name} | cols=${summary.columnCount} idx=${summary.indexCount} rows=${summary.rowCount}`);
|
|
143
|
+
await db.query(`CREATE VIEW user_post_counts AS SELECT u.id, u.name, u.email, COUNT(p.id) as post_count FROM users u LEFT JOIN posts p ON p.user_id = u.id GROUP BY u.id`);
|
|
144
|
+
const views = await db.schema.getViews();
|
|
145
|
+
for (const v of views) {
|
|
146
|
+
console.log(` View: ${v.name}`);
|
|
147
|
+
}
|
|
148
|
+
const schema = await db.schema.getSchema();
|
|
149
|
+
console.log(` Schema: ${schema.name} (${schema.tables.length} tables, ${schema.views.length} views)`);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// src/demo/features/exports.ts
|
|
153
|
+
async function demoExports(db) {
|
|
154
|
+
const users = await db.query("SELECT id, name, email, role, active FROM users LIMIT 3");
|
|
155
|
+
console.log(` CSV:
|
|
156
|
+
${toCSV(users).split("\n").slice(0, 3).join("\n ")}`);
|
|
157
|
+
console.log(` JSON pretty:
|
|
158
|
+
${toJSON(users).substring(0, 150)}...`);
|
|
159
|
+
console.log(` JSON Lines:
|
|
160
|
+
${toJSONLines(users).split("\n")[0]}`);
|
|
161
|
+
console.log(` JSON Array:
|
|
162
|
+
${toJSONArray(users).substring(0, 100)}...`);
|
|
163
|
+
const csvTab = toCSV(users, { delimiter: " ", nullValue: "<null>" });
|
|
164
|
+
console.log(` CSV (tab): ${csvTab.split("\n")[0]}`);
|
|
165
|
+
const jsonMeta = toJSON(users, { includeMeta: true });
|
|
166
|
+
const parsed = JSON.parse(jsonMeta);
|
|
167
|
+
console.log(` JSON+meta: ${parsed.meta.rowCount} rows, ${parsed.meta.columns.join(", ")}`);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// src/demo/features/validation.ts
|
|
171
|
+
async function demoValidation(db) {
|
|
172
|
+
const samples = [
|
|
173
|
+
"SELECT * FROM users",
|
|
174
|
+
"DROP TABLE users",
|
|
175
|
+
"DELETE FROM users",
|
|
176
|
+
"SELECT * FROM users WHERE 1=1"
|
|
177
|
+
];
|
|
178
|
+
for (const sql of samples) {
|
|
179
|
+
const v = validateQuery(sql, db.config.security);
|
|
180
|
+
console.log(` [${v.valid ? "VALID" : "INVALID"}] ${sql.substring(0, 40)} - ${v.errors.map((e) => e.message).join("; ")}`);
|
|
181
|
+
}
|
|
182
|
+
const types = ["SELECT * FROM users", "INSERT INTO users VALUES (1)", "UPDATE users SET name=? WHERE id=?", "DELETE FROM users WHERE id=?", "CREATE TABLE t (id INT)", "DROP TABLE users"];
|
|
183
|
+
for (const sql of types) {
|
|
184
|
+
console.log(` ${detectStatementType(sql).padEnd(10)} ${sql}`);
|
|
185
|
+
}
|
|
186
|
+
const injections = [
|
|
187
|
+
"SELECT * FROM users WHERE id = 1 OR 1=1",
|
|
188
|
+
"SELECT * FROM users; DROP TABLE users;",
|
|
189
|
+
"SELECT * FROM users WHERE name = '' OR '1'='1'"
|
|
190
|
+
];
|
|
191
|
+
for (const sql of injections) {
|
|
192
|
+
console.log(` [${detectInjection(sql) ? "MALICIOUS" : "SAFE"}] ${sql.substring(0, 50)}`);
|
|
193
|
+
}
|
|
194
|
+
const dirty = ["bad table", "123name", "drop;all"];
|
|
195
|
+
for (const n of dirty) {
|
|
196
|
+
console.log(` sanitize: "${n}" -> "${sanitizeIdentifier(n)}"`);
|
|
197
|
+
}
|
|
198
|
+
const ugly = "select u.id,count(*) from users u where u.active=1 group by u.id having count(*)>0 order by u.id";
|
|
199
|
+
console.log(` Formatted:
|
|
200
|
+
${formatSQL(ugly, { uppercase: true }).split("\n").join("\n ")}`);
|
|
201
|
+
const row = { id: 1, name: "Alice", data: Buffer.from("test") };
|
|
202
|
+
console.log(` formatResultRow: ${JSON.stringify(formatResultRow(row))}`);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// src/demo/features/security.ts
|
|
206
|
+
async function demoSecurity() {
|
|
207
|
+
const guard = new ReadOnlyGuard({ enabled: true, allowSelect: true });
|
|
208
|
+
for (const sql of ["SELECT 1", "INSERT INTO t VALUES (1)", "DROP TABLE t"]) {
|
|
209
|
+
const r = guard.checkReadOnly(sql);
|
|
210
|
+
console.log(` ReadOnlyGuard [${r.allowed ? "ALLOW" : "BLOCK"}]: ${sql}`);
|
|
211
|
+
}
|
|
212
|
+
const limiter = new RateLimiter({ windowMs: 6e4, maxQueries: 3 });
|
|
213
|
+
for (let i = 0; i < 5; i++) {
|
|
214
|
+
const r = limiter.check("demo");
|
|
215
|
+
console.log(` RateLimiter [${r.allowed ? "ALLOW" : "DENY"}] req ${i + 1} (${r.remaining} left)`);
|
|
216
|
+
}
|
|
217
|
+
limiter.destroy();
|
|
218
|
+
console.log(` requireWhere DELETE: ${validateQuery("DELETE FROM users", { requireWhere: true }).valid}`);
|
|
219
|
+
console.log(` requireWhere DELETE+WHERE: ${validateQuery("DELETE FROM users WHERE id=1", { requireWhere: true }).valid}`);
|
|
220
|
+
console.log(` banned DROP: ${validateQuery("DROP TABLE users", { bannedStatements: ["DROP", "TRUNCATE"] }).valid}`);
|
|
221
|
+
console.log(` maxQueryLength: ${validateQuery("SELECT 1", { maxQueryLength: 5 }).valid}`);
|
|
222
|
+
}
|
|
223
|
+
async function demoUtilities() {
|
|
224
|
+
const logger = new Logger({ level: "info" });
|
|
225
|
+
logger.startTimer("task");
|
|
226
|
+
await new Promise((r) => setTimeout(r, 5));
|
|
227
|
+
const elapsed = logger.endTimer("task");
|
|
228
|
+
console.log(` Logger timer: ${elapsed.toFixed(2)}ms`);
|
|
229
|
+
const qt = new QueryTimer();
|
|
230
|
+
await new Promise((r) => setTimeout(r, 5));
|
|
231
|
+
console.log(` QueryTimer: ${qt.elapsed.toFixed(2)}ms`);
|
|
232
|
+
const p = calculatePagination({ page: 3, pageSize: 20 });
|
|
233
|
+
console.log(` Pagination: offset=${p.offset}, limit=${p.limit}`);
|
|
234
|
+
const pr = createPaginatedResult(["a", "b", "c"], 50, { page: 1, pageSize: 3 });
|
|
235
|
+
console.log(` PaginatedResult: ${pr.data.length} items, page ${pr.page}/${pr.totalPages}`);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// src/demo/features/admin.ts
|
|
239
|
+
async function demoHistory(db) {
|
|
240
|
+
const hist = db.history;
|
|
241
|
+
for (const entry of hist.getRecent(4)) {
|
|
242
|
+
console.log(` [${entry.status}] ${entry.duration.toFixed(1)}ms | ${entry.query.substring(0, 60)}`);
|
|
243
|
+
}
|
|
244
|
+
const stats = hist.getStats();
|
|
245
|
+
console.log(` Stats: ${stats.totalQueries} total, ${stats.successfulQueries} ok, ${stats.failedQueries} failed, avg ${stats.avgDuration.toFixed(2)}ms`);
|
|
246
|
+
const filtered = hist.list({ status: "success", pageSize: 2 });
|
|
247
|
+
console.log(` Filtered: page ${filtered.page}/${filtered.totalPages} (${filtered.total} total)`);
|
|
248
|
+
const searched = hist.list({ search: "SELECT", pageSize: 3 });
|
|
249
|
+
console.log(` Search: ${searched.total} entries match "SELECT"`);
|
|
250
|
+
}
|
|
251
|
+
async function demoEvents(db) {
|
|
252
|
+
const collected = [];
|
|
253
|
+
db.on("query", (r) => collected.push(`query:${r.status}:${r.rowCount} rows`));
|
|
254
|
+
await db.query("SELECT 42 as answer");
|
|
255
|
+
await db.query("SELECT 84 as answer");
|
|
256
|
+
for (const e of collected) console.log(` Event: ${e}`);
|
|
257
|
+
db.removeAllListeners();
|
|
258
|
+
}
|
|
259
|
+
async function demoErrors(db) {
|
|
260
|
+
const r1 = await db.query("SELECTT invalid syntax");
|
|
261
|
+
console.log(` Bad SQL: ${r1.status} - ${r1.error}`);
|
|
262
|
+
const r2 = await db.query("INSERT INTO users (name, email) VALUES (?, ?)", { params: ["T", "t@t.com"], readOnly: true });
|
|
263
|
+
console.log(` ReadOnly: ${r2.status} - ${r2.error}`);
|
|
264
|
+
const r3 = await db.query("DELETE FROM users");
|
|
265
|
+
console.log(` Missing WHERE: ${r3.status} - ${r3.error}`);
|
|
266
|
+
}
|
|
267
|
+
async function demoExplain(db) {
|
|
268
|
+
const plan = await db.explain("SELECT u.name, COUNT(p.id) FROM users u JOIN posts p ON p.user_id = u.id GROUP BY u.name");
|
|
269
|
+
console.log(` EXPLAIN: ${plan.rowCount} plan steps (${plan.duration.toFixed(1)}ms)`);
|
|
270
|
+
}
|
|
271
|
+
async function demoStatus(db) {
|
|
272
|
+
const s = await db.status();
|
|
273
|
+
console.log(` Status: connected=${s.connected} driver=${s.driver} ver=${s.version} dbver=${s.databaseVersion}`);
|
|
274
|
+
console.log(` History: ${s.history.total} queries, ${s.history.successful} ok`);
|
|
275
|
+
const dbs = await db.getDatabases();
|
|
276
|
+
console.log(` Databases: ${dbs.join(", ")}`);
|
|
277
|
+
db.updateSecurity({ readOnly: { enabled: true, allowSelect: true } });
|
|
278
|
+
const r = await db.query("SELECT COUNT(*) as c FROM users");
|
|
279
|
+
console.log(` Dynamic security: ${r.status} (${r.rowCount} rows)`);
|
|
280
|
+
db.updateSecurity({ readOnly: { enabled: false } });
|
|
281
|
+
}
|
|
282
|
+
async function demoFactory() {
|
|
283
|
+
const db = createDashboard({ driver: { type: "sqlite" /* SQLITE */, connection: { mode: "memory" } }, logger: { level: "silent" }, autoConnect: false });
|
|
284
|
+
await db.connect();
|
|
285
|
+
const s = await db.status();
|
|
286
|
+
console.log(` Factory: v${s.version}, ${s.driver}`);
|
|
287
|
+
db.destroy();
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// src/demo/index.ts
|
|
291
|
+
var DIVIDER = "=".repeat(60);
|
|
292
|
+
function section(num, title) {
|
|
293
|
+
console.log(`
|
|
294
|
+
${DIVIDER}
|
|
295
|
+
${num}. ${title}
|
|
296
|
+
${DIVIDER}`);
|
|
297
|
+
}
|
|
298
|
+
async function main() {
|
|
299
|
+
console.log(`
|
|
300
|
+
SQL-Dashboard v1.0.1 - Feature Demo
|
|
301
|
+
${DIVIDER}`);
|
|
302
|
+
const db = await demoBasicSetup();
|
|
303
|
+
section("1", "Table Creation");
|
|
304
|
+
await demoTableCreation(db);
|
|
305
|
+
section("2", "Query Execution");
|
|
306
|
+
await demoQueries(db);
|
|
307
|
+
section("3", "Transactions & Batch");
|
|
308
|
+
await demoTransactions(db);
|
|
309
|
+
await demoBatchQueries(db);
|
|
310
|
+
section("4", "Schema Browser");
|
|
311
|
+
await demoSchema(db);
|
|
312
|
+
section("5", "Exports");
|
|
313
|
+
await demoExports(db);
|
|
314
|
+
section("6", "Validation & Formatting");
|
|
315
|
+
await demoValidation(db);
|
|
316
|
+
section("7", "Security & Utilities");
|
|
317
|
+
await demoSecurity();
|
|
318
|
+
await demoUtilities();
|
|
319
|
+
section("8", "Query History");
|
|
320
|
+
await demoHistory(db);
|
|
321
|
+
section("9", "Events & Errors");
|
|
322
|
+
await demoEvents(db);
|
|
323
|
+
await demoErrors(db);
|
|
324
|
+
section("10", "Explain & Status");
|
|
325
|
+
await demoExplain(db);
|
|
326
|
+
await demoStatus(db);
|
|
327
|
+
section("11", "Factory");
|
|
328
|
+
await demoFactory();
|
|
329
|
+
console.log(`
|
|
330
|
+
${DIVIDER}
|
|
331
|
+
All features demonstrated successfully
|
|
332
|
+
${DIVIDER}
|
|
333
|
+
`);
|
|
334
|
+
db.destroy();
|
|
335
|
+
}
|
|
336
|
+
main().catch((err) => {
|
|
337
|
+
console.error(err);
|
|
338
|
+
process.exit(1);
|
|
339
|
+
});
|
package/dist/export/index.mjs
CHANGED
|
@@ -1,71 +1,11 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
includeHeader = true,
|
|
10
|
-
quoteAll = true,
|
|
11
|
-
nullValue = "NULL"
|
|
12
|
-
} = options;
|
|
13
|
-
const lines = [];
|
|
14
|
-
if (includeHeader && result.columns.length > 0) {
|
|
15
|
-
lines.push(result.columns.map((col) => formatCSVField(col, delimiter, quoteAll)).join(delimiter));
|
|
16
|
-
}
|
|
17
|
-
for (const row of result.rows) {
|
|
18
|
-
const line = result.columns.map((col) => {
|
|
19
|
-
const value = row[col];
|
|
20
|
-
if (value === null || value === void 0) return nullValue;
|
|
21
|
-
return formatCSVField(String(value), delimiter, quoteAll);
|
|
22
|
-
});
|
|
23
|
-
lines.push(line.join(delimiter));
|
|
24
|
-
}
|
|
25
|
-
return lines.join("\r\n");
|
|
26
|
-
}
|
|
27
|
-
function formatCSVField(value, delimiter, quoteAll) {
|
|
28
|
-
const needsQuoting = quoteAll || value.includes(delimiter) || value.includes('"') || value.includes("\n") || value.includes("\r");
|
|
29
|
-
if (needsQuoting) {
|
|
30
|
-
return `"${value.replace(/"/g, '""')}"`;
|
|
31
|
-
}
|
|
32
|
-
return value;
|
|
33
|
-
}
|
|
34
|
-
function toCSVStream(result, options = {}) {
|
|
35
|
-
const { Readable } = __require("stream");
|
|
36
|
-
const csv = toCSV(result, options);
|
|
37
|
-
const stream = new Readable();
|
|
38
|
-
stream.push(csv);
|
|
39
|
-
stream.push(null);
|
|
40
|
-
return stream;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
// src/export/json.ts
|
|
44
|
-
function toJSON(result, options = {}) {
|
|
45
|
-
const {
|
|
46
|
-
pretty = true,
|
|
47
|
-
indent = 2,
|
|
48
|
-
includeMeta = false
|
|
49
|
-
} = options;
|
|
50
|
-
if (includeMeta) {
|
|
51
|
-
return JSON.stringify({
|
|
52
|
-
meta: {
|
|
53
|
-
rowCount: result.rowCount,
|
|
54
|
-
columns: result.columns,
|
|
55
|
-
duration: result.duration,
|
|
56
|
-
status: result.status
|
|
57
|
-
},
|
|
58
|
-
data: result.rows
|
|
59
|
-
}, null, pretty ? indent : void 0);
|
|
60
|
-
}
|
|
61
|
-
return JSON.stringify(result.rows, null, pretty ? indent : void 0);
|
|
62
|
-
}
|
|
63
|
-
function toJSONLines(result) {
|
|
64
|
-
return result.rows.map((row) => JSON.stringify(row)).join("\n");
|
|
65
|
-
}
|
|
66
|
-
function toJSONArray(result) {
|
|
67
|
-
return JSON.stringify(result.rows);
|
|
68
|
-
}
|
|
2
|
+
toCSV,
|
|
3
|
+
toCSVStream,
|
|
4
|
+
toJSON,
|
|
5
|
+
toJSONArray,
|
|
6
|
+
toJSONLines
|
|
7
|
+
} from "../chunk-ABIHDUB7.mjs";
|
|
8
|
+
import "../chunk-OCL5Y3AH.mjs";
|
|
69
9
|
export {
|
|
70
10
|
toCSV,
|
|
71
11
|
toCSVStream,
|
package/dist/index.js
CHANGED
|
@@ -25907,7 +25907,7 @@ function extractTableNames(sql) {
|
|
|
25907
25907
|
}
|
|
25908
25908
|
function validateQuery(sql, security) {
|
|
25909
25909
|
const errors = [];
|
|
25910
|
-
|
|
25910
|
+
let trimmedSql = sql.trim();
|
|
25911
25911
|
if (!trimmedSql) {
|
|
25912
25912
|
errors.push({
|
|
25913
25913
|
code: "EMPTY_QUERY",
|
|
@@ -25917,7 +25917,7 @@ function validateQuery(sql, security) {
|
|
|
25917
25917
|
return { valid: false, errors, normalizedQuery: sql, statementType: "UNKNOWN", isReadOnly: true, tables: [] };
|
|
25918
25918
|
}
|
|
25919
25919
|
if (!trimmedSql.endsWith(";")) {
|
|
25920
|
-
trimmedSql + ";";
|
|
25920
|
+
trimmedSql = trimmedSql + ";";
|
|
25921
25921
|
}
|
|
25922
25922
|
const statementType = detectStatementType(trimmedSql);
|
|
25923
25923
|
const detectedTables = extractTableNames(trimmedSql);
|
|
@@ -26203,7 +26203,7 @@ function truncateResult(rows, maxRows) {
|
|
|
26203
26203
|
var SQLDashboard = class extends _eventemitter3.EventEmitter {
|
|
26204
26204
|
constructor(options) {
|
|
26205
26205
|
super();
|
|
26206
|
-
this.version = "1.0.
|
|
26206
|
+
this.version = "1.0.1";
|
|
26207
26207
|
this.connected = false;
|
|
26208
26208
|
this.connectionPromise = null;
|
|
26209
26209
|
this.config = {
|
package/dist/index.mjs
CHANGED
|
@@ -25907,7 +25907,7 @@ function extractTableNames(sql) {
|
|
|
25907
25907
|
}
|
|
25908
25908
|
function validateQuery(sql, security) {
|
|
25909
25909
|
const errors = [];
|
|
25910
|
-
|
|
25910
|
+
let trimmedSql = sql.trim();
|
|
25911
25911
|
if (!trimmedSql) {
|
|
25912
25912
|
errors.push({
|
|
25913
25913
|
code: "EMPTY_QUERY",
|
|
@@ -25917,7 +25917,7 @@ function validateQuery(sql, security) {
|
|
|
25917
25917
|
return { valid: false, errors, normalizedQuery: sql, statementType: "UNKNOWN", isReadOnly: true, tables: [] };
|
|
25918
25918
|
}
|
|
25919
25919
|
if (!trimmedSql.endsWith(";")) {
|
|
25920
|
-
trimmedSql + ";";
|
|
25920
|
+
trimmedSql = trimmedSql + ";";
|
|
25921
25921
|
}
|
|
25922
25922
|
const statementType = detectStatementType(trimmedSql);
|
|
25923
25923
|
const detectedTables = extractTableNames(trimmedSql);
|
|
@@ -26203,7 +26203,7 @@ function truncateResult(rows, maxRows) {
|
|
|
26203
26203
|
var SQLDashboard = class extends EventEmitter {
|
|
26204
26204
|
constructor(options) {
|
|
26205
26205
|
super();
|
|
26206
|
-
this.version = "1.0.
|
|
26206
|
+
this.version = "1.0.1";
|
|
26207
26207
|
this.connected = false;
|
|
26208
26208
|
this.connectionPromise = null;
|
|
26209
26209
|
this.config = {
|
|
@@ -90365,7 +90365,7 @@ function extractTableNames(sql) {
|
|
|
90365
90365
|
}
|
|
90366
90366
|
function validateQuery(sql, security) {
|
|
90367
90367
|
const errors = [];
|
|
90368
|
-
|
|
90368
|
+
let trimmedSql = sql.trim();
|
|
90369
90369
|
if (!trimmedSql) {
|
|
90370
90370
|
errors.push({
|
|
90371
90371
|
code: "EMPTY_QUERY",
|
|
@@ -90375,7 +90375,7 @@ function validateQuery(sql, security) {
|
|
|
90375
90375
|
return { valid: false, errors, normalizedQuery: sql, statementType: "UNKNOWN", isReadOnly: true, tables: [] };
|
|
90376
90376
|
}
|
|
90377
90377
|
if (!trimmedSql.endsWith(";")) {
|
|
90378
|
-
trimmedSql + ";";
|
|
90378
|
+
trimmedSql = trimmedSql + ";";
|
|
90379
90379
|
}
|
|
90380
90380
|
const statementType = detectStatementType(trimmedSql);
|
|
90381
90381
|
const detectedTables = extractTableNames(trimmedSql);
|
|
@@ -90487,7 +90487,7 @@ function truncateResult(rows, maxRows) {
|
|
|
90487
90487
|
var SQLDashboard = class extends import_eventemitter3.EventEmitter {
|
|
90488
90488
|
constructor(options) {
|
|
90489
90489
|
super();
|
|
90490
|
-
this.version = "1.0.
|
|
90490
|
+
this.version = "1.0.1";
|
|
90491
90491
|
this.connected = false;
|
|
90492
90492
|
this.connectionPromise = null;
|
|
90493
90493
|
this.config = {
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import {
|
|
2
2
|
SQLDashboard
|
|
3
|
-
} from "../chunk-
|
|
3
|
+
} from "../chunk-RU3R3RGI.mjs";
|
|
4
4
|
import "../chunk-MTCZXLV5.mjs";
|
|
5
|
-
import "../chunk-
|
|
5
|
+
import "../chunk-62XBCHG5.mjs";
|
|
6
|
+
import "../chunk-HIQUIRDJ.mjs";
|
|
6
7
|
import "../chunk-YGKUVVJT.mjs";
|
|
7
8
|
import "../chunk-P4QE6SGC.mjs";
|
|
8
|
-
import "../chunk-HIQUIRDJ.mjs";
|
|
9
9
|
import {
|
|
10
10
|
__require
|
|
11
11
|
} from "../chunk-OCL5Y3AH.mjs";
|
|
@@ -90365,7 +90365,7 @@ function extractTableNames(sql) {
|
|
|
90365
90365
|
}
|
|
90366
90366
|
function validateQuery(sql, security) {
|
|
90367
90367
|
const errors = [];
|
|
90368
|
-
|
|
90368
|
+
let trimmedSql = sql.trim();
|
|
90369
90369
|
if (!trimmedSql) {
|
|
90370
90370
|
errors.push({
|
|
90371
90371
|
code: "EMPTY_QUERY",
|
|
@@ -90375,7 +90375,7 @@ function validateQuery(sql, security) {
|
|
|
90375
90375
|
return { valid: false, errors, normalizedQuery: sql, statementType: "UNKNOWN", isReadOnly: true, tables: [] };
|
|
90376
90376
|
}
|
|
90377
90377
|
if (!trimmedSql.endsWith(";")) {
|
|
90378
|
-
trimmedSql + ";";
|
|
90378
|
+
trimmedSql = trimmedSql + ";";
|
|
90379
90379
|
}
|
|
90380
90380
|
const statementType = detectStatementType(trimmedSql);
|
|
90381
90381
|
const detectedTables = extractTableNames(trimmedSql);
|
|
@@ -90487,7 +90487,7 @@ function truncateResult(rows, maxRows) {
|
|
|
90487
90487
|
var SQLDashboard = class extends import_eventemitter3.EventEmitter {
|
|
90488
90488
|
constructor(options) {
|
|
90489
90489
|
super();
|
|
90490
|
-
this.version = "1.0.
|
|
90490
|
+
this.version = "1.0.1";
|
|
90491
90491
|
this.connected = false;
|
|
90492
90492
|
this.connectionPromise = null;
|
|
90493
90493
|
this.config = {
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import {
|
|
2
2
|
SQLDashboard
|
|
3
|
-
} from "../chunk-
|
|
3
|
+
} from "../chunk-RU3R3RGI.mjs";
|
|
4
4
|
import "../chunk-MTCZXLV5.mjs";
|
|
5
|
-
import "../chunk-
|
|
5
|
+
import "../chunk-62XBCHG5.mjs";
|
|
6
|
+
import "../chunk-HIQUIRDJ.mjs";
|
|
6
7
|
import "../chunk-YGKUVVJT.mjs";
|
|
7
8
|
import "../chunk-P4QE6SGC.mjs";
|
|
8
|
-
import "../chunk-HIQUIRDJ.mjs";
|
|
9
9
|
import "../chunk-OCL5Y3AH.mjs";
|
|
10
10
|
|
|
11
11
|
// src/middleware/fastify.ts
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sql-dashboard",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"description": "🚀 Professional SQL management dashboard for admin panels - Execute queries, browse schemas, manage databases with ease",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"sql",
|
|
@@ -57,6 +57,11 @@
|
|
|
57
57
|
"types": "./dist/export/index.d.ts",
|
|
58
58
|
"import": "./dist/export/index.mjs",
|
|
59
59
|
"require": "./dist/export/index.js"
|
|
60
|
+
},
|
|
61
|
+
"./demo": {
|
|
62
|
+
"types": "./dist/demo/index.d.ts",
|
|
63
|
+
"import": "./dist/demo/index.mjs",
|
|
64
|
+
"require": "./dist/demo/index.js"
|
|
60
65
|
}
|
|
61
66
|
},
|
|
62
67
|
"files": [
|
|
@@ -71,7 +76,7 @@
|
|
|
71
76
|
"scripts": {
|
|
72
77
|
"build": "tsup src/index.ts --format cjs,esm --dts --clean --splitting --out-dir dist",
|
|
73
78
|
"build:all": "npm run build && npm run build:subpaths",
|
|
74
|
-
"build:subpaths": "tsup src/middleware/express.ts src/middleware/fastify.ts src/export/index.ts src/drivers/mysql.driver.ts src/drivers/postgres.driver.ts src/drivers/mssql.driver.ts --format cjs,esm --dts --out-dir dist",
|
|
79
|
+
"build:subpaths": "tsup src/middleware/express.ts src/middleware/fastify.ts src/export/index.ts src/drivers/mysql.driver.ts src/drivers/postgres.driver.ts src/drivers/mssql.driver.ts src/demo/index.ts --format cjs,esm --dts --out-dir dist",
|
|
75
80
|
"dev": "tsup src/index.ts --format cjs,esm --dts --watch",
|
|
76
81
|
"test": "vitest run",
|
|
77
82
|
"test:watch": "vitest",
|
|
@@ -86,6 +91,7 @@
|
|
|
86
91
|
"example:basic": "tsx examples/basic.ts",
|
|
87
92
|
"example:express": "tsx examples/express.ts",
|
|
88
93
|
"example:admin": "tsx examples/admin-panel.ts",
|
|
94
|
+
"demo": "tsx src/demo/index.ts",
|
|
89
95
|
"release": "npm run build:all && npm run test && npm run lint"
|
|
90
96
|
},
|
|
91
97
|
"dependencies": {
|