drixio 1.1.6 → 1.1.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/dist/cli.js +1081 -215
- package/dist/studio/assets/index-Dos1aeov.css +1 -0
- package/dist/studio/assets/index-DsIbymPZ.js +1228 -0
- package/dist/studio/index.html +3 -3
- package/package.json +1 -1
- package/dist/studio/assets/index-BYN38H7U.css +0 -1
- package/dist/studio/assets/index-iu78q-Xp.js +0 -496
package/dist/cli.js
CHANGED
|
@@ -47,7 +47,7 @@ function printCustomDashboard(title, rows) {
|
|
|
47
47
|
);
|
|
48
48
|
}
|
|
49
49
|
function printDashboard(dbConfig) {
|
|
50
|
-
const headerTitle = ` Lightweight Interactive TUI Database Client \u2022 v${
|
|
50
|
+
const headerTitle = ` Lightweight Interactive TUI Database Client \u2022 v${process.env.npm_package_version} `;
|
|
51
51
|
let dbTypeVal = "None";
|
|
52
52
|
let targetVal = "-";
|
|
53
53
|
let sourceVal = "None";
|
|
@@ -156,14 +156,49 @@ var init_sqlite = __esm({
|
|
|
156
156
|
const info = pragmaQuery.all();
|
|
157
157
|
const fkQuery = db.prepare(`PRAGMA foreign_key_list(${quoted})`);
|
|
158
158
|
const fks = fkQuery.all();
|
|
159
|
+
const indexListQuery = db.prepare(`PRAGMA index_list(${quoted})`);
|
|
160
|
+
const indexList = indexListQuery.all();
|
|
161
|
+
const uniqueCols = /* @__PURE__ */ new Set();
|
|
162
|
+
for (const idx of indexList) {
|
|
163
|
+
if (idx.unique === 1) {
|
|
164
|
+
const infoQuery = db.prepare(
|
|
165
|
+
`PRAGMA index_info(${this.quoteIdentifier(idx.name)})`
|
|
166
|
+
);
|
|
167
|
+
const cols = infoQuery.all();
|
|
168
|
+
if (cols.length === 1 && cols[0].name) {
|
|
169
|
+
uniqueCols.add(cols[0].name);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
const enumMap = /* @__PURE__ */ new Map();
|
|
174
|
+
try {
|
|
175
|
+
const createSql = db.prepare(
|
|
176
|
+
`SELECT sql FROM sqlite_master WHERE type='table' AND name=${quoted}`
|
|
177
|
+
).get();
|
|
178
|
+
if (createSql?.sql) {
|
|
179
|
+
const checkRegex = /CHECK\s*\(\s*["']?(\w+)["']?\s+IN\s*\(([^)]+)\)\s*\)/gi;
|
|
180
|
+
let match;
|
|
181
|
+
while ((match = checkRegex.exec(createSql.sql)) !== null) {
|
|
182
|
+
const colName = match[1];
|
|
183
|
+
const valuesStr = match[2];
|
|
184
|
+
const values = valuesStr.split(",").map((s) => s.trim().replace(/^'|'$/g, "")).filter((s) => s.length > 0);
|
|
185
|
+
if (values.length > 0) {
|
|
186
|
+
enumMap.set(colName, values);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
} catch {
|
|
191
|
+
}
|
|
159
192
|
return info.map((col) => {
|
|
160
193
|
const fk = fks.find((f) => f.from === col.name);
|
|
161
194
|
return {
|
|
162
195
|
name: col.name,
|
|
163
196
|
type: col.type,
|
|
164
197
|
isPk: col.pk > 0,
|
|
165
|
-
nullable: col.notnull === 0,
|
|
198
|
+
nullable: col.pk > 0 ? false : col.notnull === 0,
|
|
199
|
+
isUnique: col.pk > 0 || uniqueCols.has(col.name),
|
|
166
200
|
defaultValue: col.dflt_value != null ? String(col.dflt_value) : void 0,
|
|
201
|
+
enumValues: enumMap.get(col.name),
|
|
167
202
|
fkTarget: fk ? { table: fk.table, column: fk.to } : void 0
|
|
168
203
|
};
|
|
169
204
|
});
|
|
@@ -176,7 +211,9 @@ var init_sqlite = __esm({
|
|
|
176
211
|
const indexes = [];
|
|
177
212
|
for (const idx of indexList) {
|
|
178
213
|
if (idx.origin === "pk") continue;
|
|
179
|
-
const indexInfoQuery = db.prepare(
|
|
214
|
+
const indexInfoQuery = db.prepare(
|
|
215
|
+
`PRAGMA index_info(${this.quoteIdentifier(idx.name)})`
|
|
216
|
+
);
|
|
180
217
|
const columns = indexInfoQuery.all();
|
|
181
218
|
indexes.push({
|
|
182
219
|
name: idx.name,
|
|
@@ -204,20 +241,38 @@ var init_sqlite = __esm({
|
|
|
204
241
|
}
|
|
205
242
|
async query(sql) {
|
|
206
243
|
const db = await this.getDb();
|
|
207
|
-
const
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
244
|
+
const stripped = sql.replace(
|
|
245
|
+
/^(\s*--[^\n]*\n|\s*\/\*[\s\S]*?\*\/\s*|\s+)*/g,
|
|
246
|
+
""
|
|
247
|
+
);
|
|
248
|
+
const upper = stripped.toUpperCase();
|
|
249
|
+
const isRead = upper.startsWith("SELECT") || upper.startsWith("PRAGMA") || upper.startsWith("EXPLAIN") || upper.startsWith("WITH") || upper.startsWith("(");
|
|
250
|
+
const hasReturning = /\bRETURNING\b/i.test(sql);
|
|
251
|
+
if (isRead || hasReturning) {
|
|
252
|
+
const stmt = db.prepare(sql);
|
|
253
|
+
const rows = stmt.all();
|
|
212
254
|
let columns = [];
|
|
213
255
|
if (rows.length > 0) {
|
|
214
256
|
columns = Object.keys(rows[0]);
|
|
257
|
+
} else {
|
|
258
|
+
try {
|
|
259
|
+
const colMeta = stmt.columns();
|
|
260
|
+
if (Array.isArray(colMeta)) {
|
|
261
|
+
columns = colMeta.map((c) => c.name || c.column);
|
|
262
|
+
}
|
|
263
|
+
} catch {
|
|
264
|
+
}
|
|
215
265
|
}
|
|
216
266
|
return { columns, rows };
|
|
217
267
|
} else {
|
|
218
|
-
const
|
|
219
|
-
|
|
220
|
-
|
|
268
|
+
const stmt = db.prepare(sql);
|
|
269
|
+
const result = stmt.run();
|
|
270
|
+
const changes = result.changes ?? 0;
|
|
271
|
+
return {
|
|
272
|
+
columns: ["Result", "AffectedRows"],
|
|
273
|
+
rows: [{ Result: "Success", AffectedRows: changes }],
|
|
274
|
+
affectedRows: changes
|
|
275
|
+
};
|
|
221
276
|
}
|
|
222
277
|
}
|
|
223
278
|
async executeSql(sql) {
|
|
@@ -238,9 +293,108 @@ var init_sqlite = __esm({
|
|
|
238
293
|
const placeholders = cols.map(() => "?").join(", ");
|
|
239
294
|
const sql = `INSERT INTO ${this.quoteIdentifier(tableName)} (${colsQuoted}) VALUES (${placeholders})`;
|
|
240
295
|
const stmt = db.prepare(sql);
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
296
|
+
db.exec("BEGIN");
|
|
297
|
+
try {
|
|
298
|
+
for (const row of rows) {
|
|
299
|
+
const values = cols.map((c) => row[c]);
|
|
300
|
+
stmt.run(...values);
|
|
301
|
+
}
|
|
302
|
+
db.exec("COMMIT");
|
|
303
|
+
} catch (e) {
|
|
304
|
+
db.exec("ROLLBACK");
|
|
305
|
+
throw e;
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
async recreateTable(tableName, newColumns, renames = {}) {
|
|
309
|
+
const db = await this.getDb();
|
|
310
|
+
const quotedOldTable = this.quoteIdentifier(tableName);
|
|
311
|
+
const tableCheck = db.prepare(
|
|
312
|
+
`SELECT name FROM sqlite_master WHERE type = 'table' AND name = ?`
|
|
313
|
+
).get(tableName);
|
|
314
|
+
if (!tableCheck) {
|
|
315
|
+
throw new Error(`Table "${tableName}" does not exist.`);
|
|
316
|
+
}
|
|
317
|
+
const indexRows = db.prepare(
|
|
318
|
+
`SELECT name, sql FROM sqlite_master WHERE type = 'index' AND tbl_name = ? AND sql IS NOT NULL`
|
|
319
|
+
).all(tableName);
|
|
320
|
+
const tempTableName = `_drixio_recreate_${Date.now()}`;
|
|
321
|
+
const quotedTempTable = this.quoteIdentifier(tempTableName);
|
|
322
|
+
const pkColumns = newColumns.filter((c) => c.isPk);
|
|
323
|
+
const isCompositePk = pkColumns.length > 1;
|
|
324
|
+
const colDefs = [];
|
|
325
|
+
for (const col of newColumns) {
|
|
326
|
+
let def = `${this.quoteIdentifier(col.name)} ${col.type || "TEXT"}`;
|
|
327
|
+
if (col.isPk && !isCompositePk) {
|
|
328
|
+
def += " PRIMARY KEY";
|
|
329
|
+
}
|
|
330
|
+
if (!col.nullable && (!col.isPk || isCompositePk)) {
|
|
331
|
+
def += " NOT NULL";
|
|
332
|
+
}
|
|
333
|
+
if (col.defaultValue !== void 0 && col.defaultValue !== null && col.defaultValue !== "") {
|
|
334
|
+
def += ` DEFAULT '${String(col.defaultValue).replace(/'/g, "''")}'`;
|
|
335
|
+
}
|
|
336
|
+
if (col.fkTarget && col.fkTarget.table && col.fkTarget.column) {
|
|
337
|
+
def += ` REFERENCES ${this.quoteIdentifier(col.fkTarget.table)}(${this.quoteIdentifier(col.fkTarget.column)})`;
|
|
338
|
+
}
|
|
339
|
+
if (col.isUnique && (!col.isPk || isCompositePk)) {
|
|
340
|
+
def += " UNIQUE";
|
|
341
|
+
}
|
|
342
|
+
colDefs.push(def);
|
|
343
|
+
}
|
|
344
|
+
if (isCompositePk) {
|
|
345
|
+
const pkColsQuoted = pkColumns.map((c) => this.quoteIdentifier(c.name)).join(", ");
|
|
346
|
+
colDefs.push(`PRIMARY KEY (${pkColsQuoted})`);
|
|
347
|
+
}
|
|
348
|
+
const createSql = `CREATE TABLE ${quotedTempTable} (
|
|
349
|
+
${colDefs.join(",\n ")}
|
|
350
|
+
);`;
|
|
351
|
+
const newToOldMap = {};
|
|
352
|
+
for (const [oldName, newName] of Object.entries(renames)) {
|
|
353
|
+
newToOldMap[newName] = oldName;
|
|
354
|
+
}
|
|
355
|
+
const oldTableCols = db.prepare(`PRAGMA table_info(${quotedOldTable})`).all().map((r) => r.name);
|
|
356
|
+
const copyNewCols = [];
|
|
357
|
+
const copyOldCols = [];
|
|
358
|
+
for (const col of newColumns) {
|
|
359
|
+
const sourceOldName = newToOldMap[col.name] || col.name;
|
|
360
|
+
if (oldTableCols.includes(sourceOldName)) {
|
|
361
|
+
copyNewCols.push(this.quoteIdentifier(col.name));
|
|
362
|
+
copyOldCols.push(this.quoteIdentifier(sourceOldName));
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
let copySql = "";
|
|
366
|
+
if (copyNewCols.length > 0) {
|
|
367
|
+
copySql = `INSERT INTO ${quotedTempTable} (${copyNewCols.join(", ")}) SELECT ${copyOldCols.join(", ")} FROM ${quotedOldTable};`;
|
|
368
|
+
}
|
|
369
|
+
db.exec("PRAGMA foreign_keys = OFF;");
|
|
370
|
+
db.exec("BEGIN TRANSACTION;");
|
|
371
|
+
try {
|
|
372
|
+
db.exec(createSql);
|
|
373
|
+
if (copySql) {
|
|
374
|
+
db.exec(copySql);
|
|
375
|
+
}
|
|
376
|
+
db.exec(`DROP TABLE ${quotedOldTable};`);
|
|
377
|
+
db.exec(`ALTER TABLE ${quotedTempTable} RENAME TO ${quotedOldTable};`);
|
|
378
|
+
for (const idx of indexRows) {
|
|
379
|
+
if (!idx.sql) continue;
|
|
380
|
+
let idxSql = idx.sql;
|
|
381
|
+
for (const [oldName, newName] of Object.entries(renames)) {
|
|
382
|
+
idxSql = idxSql.replace(
|
|
383
|
+
new RegExp(`\\b${oldName}\\b`, "g"),
|
|
384
|
+
newName
|
|
385
|
+
);
|
|
386
|
+
}
|
|
387
|
+
try {
|
|
388
|
+
db.exec(idxSql);
|
|
389
|
+
} catch {
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
db.exec("COMMIT;");
|
|
393
|
+
} catch (err) {
|
|
394
|
+
db.exec("ROLLBACK;");
|
|
395
|
+
throw err;
|
|
396
|
+
} finally {
|
|
397
|
+
db.exec("PRAGMA foreign_keys = ON;");
|
|
244
398
|
}
|
|
245
399
|
}
|
|
246
400
|
};
|
|
@@ -254,45 +408,54 @@ var init_postgres = __esm({
|
|
|
254
408
|
"src/adapters/postgres.ts"() {
|
|
255
409
|
"use strict";
|
|
256
410
|
PostgresAdapter = class {
|
|
257
|
-
|
|
258
|
-
|
|
411
|
+
connectionString;
|
|
412
|
+
pool = null;
|
|
259
413
|
constructor(connection) {
|
|
260
|
-
this.
|
|
261
|
-
connectionString: connection
|
|
262
|
-
});
|
|
414
|
+
this.connectionString = connection;
|
|
263
415
|
}
|
|
264
|
-
|
|
265
|
-
if (!this.
|
|
266
|
-
|
|
267
|
-
|
|
416
|
+
getPool() {
|
|
417
|
+
if (!this.pool) {
|
|
418
|
+
this.pool = new pg.Pool({
|
|
419
|
+
connectionString: this.connectionString
|
|
420
|
+
});
|
|
421
|
+
this.pool.on("error", () => {
|
|
422
|
+
});
|
|
268
423
|
}
|
|
424
|
+
return this.pool;
|
|
269
425
|
}
|
|
270
426
|
quoteIdentifier(name) {
|
|
271
427
|
return `"${name.replace(/"/g, '""')}"`;
|
|
272
428
|
}
|
|
273
429
|
async getStatus() {
|
|
274
430
|
try {
|
|
275
|
-
await this.
|
|
276
|
-
|
|
431
|
+
const dbRes = await this.getPool().query(
|
|
432
|
+
"SELECT current_database() as db, version() as version"
|
|
433
|
+
);
|
|
277
434
|
const dbName = dbRes.rows[0]?.db;
|
|
278
435
|
const version = dbRes.rows[0]?.version?.split(" ")[1] || dbRes.rows[0]?.version;
|
|
279
436
|
let activeConnections = 0;
|
|
280
437
|
let transactions = 0;
|
|
281
438
|
let uptime = 0;
|
|
282
439
|
try {
|
|
283
|
-
const uptimeRes = await this.
|
|
440
|
+
const uptimeRes = await this.getPool().query(
|
|
441
|
+
"SELECT EXTRACT(EPOCH FROM (now() - pg_postmaster_start_time())) as uptime"
|
|
442
|
+
);
|
|
284
443
|
uptime = parseInt(uptimeRes.rows[0]?.uptime || "0", 10);
|
|
285
444
|
} catch (e) {
|
|
286
445
|
}
|
|
287
446
|
try {
|
|
288
|
-
const statRes = await this.
|
|
447
|
+
const statRes = await this.getPool().query(
|
|
448
|
+
"SELECT sum(numbackends) as conns, sum(xact_commit + xact_rollback) as txs FROM pg_stat_database"
|
|
449
|
+
);
|
|
289
450
|
activeConnections = parseInt(statRes.rows[0]?.conns || "0", 10);
|
|
290
451
|
transactions = parseInt(statRes.rows[0]?.txs || "0", 10);
|
|
291
452
|
} catch (e) {
|
|
292
453
|
}
|
|
293
454
|
let sizeBytes = 0;
|
|
294
455
|
try {
|
|
295
|
-
const sizeRes = await this.
|
|
456
|
+
const sizeRes = await this.getPool().query(
|
|
457
|
+
"SELECT pg_database_size(current_database()) as size"
|
|
458
|
+
);
|
|
296
459
|
sizeBytes = parseInt(sizeRes.rows[0]?.size || "0", 10);
|
|
297
460
|
} catch (e) {
|
|
298
461
|
}
|
|
@@ -314,18 +477,16 @@ var init_postgres = __esm({
|
|
|
314
477
|
}
|
|
315
478
|
}
|
|
316
479
|
async getTables() {
|
|
317
|
-
await this.connectIfNecessary();
|
|
318
480
|
const query = `
|
|
319
481
|
SELECT tablename
|
|
320
482
|
FROM pg_catalog.pg_tables
|
|
321
|
-
WHERE schemaname
|
|
483
|
+
WHERE schemaname = 'public'
|
|
322
484
|
ORDER BY tablename;
|
|
323
485
|
`;
|
|
324
|
-
const res = await this.
|
|
486
|
+
const res = await this.getPool().query(query);
|
|
325
487
|
return res.rows.map((row) => row.tablename);
|
|
326
488
|
}
|
|
327
489
|
async getSchema(tableName) {
|
|
328
|
-
await this.connectIfNecessary();
|
|
329
490
|
const query = `
|
|
330
491
|
SELECT c.column_name, c.data_type, c.is_nullable, c.column_default,
|
|
331
492
|
(SELECT count(*)
|
|
@@ -336,6 +497,15 @@ var init_postgres = __esm({
|
|
|
336
497
|
AND kcu.table_name = c.table_name
|
|
337
498
|
AND kcu.column_name = c.column_name
|
|
338
499
|
AND kcu.table_schema = c.table_schema) as is_pk,
|
|
500
|
+
(SELECT count(*)
|
|
501
|
+
FROM information_schema.table_constraints tc2
|
|
502
|
+
JOIN information_schema.constraint_column_usage ccu2
|
|
503
|
+
ON tc2.constraint_name = ccu2.constraint_name
|
|
504
|
+
AND tc2.table_schema = ccu2.table_schema
|
|
505
|
+
WHERE tc2.constraint_type = 'UNIQUE'
|
|
506
|
+
AND tc2.table_name = c.table_name
|
|
507
|
+
AND tc2.table_schema = c.table_schema
|
|
508
|
+
AND ccu2.column_name = c.column_name) as is_unique,
|
|
339
509
|
(SELECT ccu.table_name || '.' || ccu.column_name
|
|
340
510
|
FROM information_schema.table_constraints tc
|
|
341
511
|
JOIN information_schema.key_column_usage kcu
|
|
@@ -351,7 +521,7 @@ var init_postgres = __esm({
|
|
|
351
521
|
WHERE c.table_name = $1 AND c.table_schema = 'public'
|
|
352
522
|
ORDER BY c.ordinal_position;
|
|
353
523
|
`;
|
|
354
|
-
const res = await this.
|
|
524
|
+
const res = await this.getPool().query(query, [tableName]);
|
|
355
525
|
return res.rows.map((col) => {
|
|
356
526
|
let fkTarget;
|
|
357
527
|
if (col.fk_target) {
|
|
@@ -362,14 +532,14 @@ var init_postgres = __esm({
|
|
|
362
532
|
name: col.column_name,
|
|
363
533
|
type: col.data_type,
|
|
364
534
|
isPk: parseInt(col.is_pk) > 0,
|
|
365
|
-
nullable: col.is_nullable === "YES",
|
|
535
|
+
nullable: parseInt(col.is_pk) > 0 ? false : col.is_nullable === "YES",
|
|
536
|
+
isUnique: parseInt(col.is_pk) > 0 || parseInt(col.is_unique) > 0,
|
|
366
537
|
defaultValue: col.column_default != null ? String(col.column_default) : void 0,
|
|
367
538
|
fkTarget
|
|
368
539
|
};
|
|
369
540
|
});
|
|
370
541
|
}
|
|
371
542
|
async getIndexes(tableName) {
|
|
372
|
-
await this.connectIfNecessary();
|
|
373
543
|
const query = `
|
|
374
544
|
SELECT
|
|
375
545
|
i.relname as index_name,
|
|
@@ -388,10 +558,11 @@ var init_postgres = __esm({
|
|
|
388
558
|
AND a.attnum = ANY(ix.indkey)
|
|
389
559
|
AND t.relkind = 'r'
|
|
390
560
|
AND t.relname = $1
|
|
561
|
+
AND t.relnamespace = 'public'::regnamespace
|
|
391
562
|
ORDER BY
|
|
392
|
-
i.relname, a.attnum;
|
|
563
|
+
i.relname, array_position(ix.indkey, a.attnum);
|
|
393
564
|
`;
|
|
394
|
-
const res = await this.
|
|
565
|
+
const res = await this.getPool().query(query, [tableName]);
|
|
395
566
|
const rows = res.rows;
|
|
396
567
|
const indexMap = /* @__PURE__ */ new Map();
|
|
397
568
|
for (const row of rows) {
|
|
@@ -409,7 +580,6 @@ var init_postgres = __esm({
|
|
|
409
580
|
return Array.from(indexMap.values());
|
|
410
581
|
}
|
|
411
582
|
async getData(tableName, limit = 50, offset = 0, whereClause, orderBy) {
|
|
412
|
-
await this.connectIfNecessary();
|
|
413
583
|
const schema = await this.getSchema(tableName);
|
|
414
584
|
const columns = schema.map((col) => col.name);
|
|
415
585
|
let sql = `SELECT * FROM ${this.quoteIdentifier(tableName)}`;
|
|
@@ -420,38 +590,50 @@ var init_postgres = __esm({
|
|
|
420
590
|
sql += ` ORDER BY ${this.quoteIdentifier(orderBy.col)} ${orderBy.asc ? "ASC" : "DESC"}`;
|
|
421
591
|
}
|
|
422
592
|
sql += ` LIMIT $1 OFFSET $2`;
|
|
423
|
-
const res = await this.
|
|
593
|
+
const res = await this.getPool().query(sql, [limit, offset]);
|
|
424
594
|
return { columns, rows: res.rows };
|
|
425
595
|
}
|
|
426
596
|
async query(sql) {
|
|
427
|
-
await this.
|
|
428
|
-
const res = await this.client.query(sql);
|
|
597
|
+
const res = await this.getPool().query(sql);
|
|
429
598
|
let columns = [];
|
|
430
599
|
if (res.fields) {
|
|
431
600
|
columns = res.fields.map((f) => f.name);
|
|
432
601
|
}
|
|
433
|
-
return {
|
|
602
|
+
return {
|
|
603
|
+
columns,
|
|
604
|
+
rows: res.rows || [],
|
|
605
|
+
affectedRows: typeof res.rowCount === "number" ? res.rowCount : void 0
|
|
606
|
+
};
|
|
434
607
|
}
|
|
435
608
|
async executeSql(sql) {
|
|
436
|
-
await this.
|
|
437
|
-
await this.client.query(sql);
|
|
609
|
+
await this.getPool().query(sql);
|
|
438
610
|
}
|
|
439
611
|
async close() {
|
|
440
|
-
if (this.
|
|
441
|
-
await this.
|
|
442
|
-
this.
|
|
612
|
+
if (this.pool) {
|
|
613
|
+
await this.pool.end();
|
|
614
|
+
this.pool = null;
|
|
443
615
|
}
|
|
444
616
|
}
|
|
445
617
|
async insert(tableName, rows) {
|
|
446
618
|
if (rows.length === 0) return;
|
|
447
|
-
|
|
448
|
-
const
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
const
|
|
452
|
-
|
|
453
|
-
const
|
|
454
|
-
|
|
619
|
+
const pool = this.getPool();
|
|
620
|
+
const client = await pool.connect();
|
|
621
|
+
try {
|
|
622
|
+
const cols = Object.keys(rows[0]);
|
|
623
|
+
const colsQuoted = cols.map((c) => this.quoteIdentifier(c)).join(", ");
|
|
624
|
+
await client.query("BEGIN");
|
|
625
|
+
for (const row of rows) {
|
|
626
|
+
const placeholders = cols.map((_, i) => `$${i + 1}`).join(", ");
|
|
627
|
+
const sql = `INSERT INTO ${this.quoteIdentifier(tableName)} (${colsQuoted}) VALUES (${placeholders})`;
|
|
628
|
+
const values = cols.map((c) => row[c]);
|
|
629
|
+
await client.query(sql, values);
|
|
630
|
+
}
|
|
631
|
+
await client.query("COMMIT");
|
|
632
|
+
} catch (e) {
|
|
633
|
+
await client.query("ROLLBACK");
|
|
634
|
+
throw e;
|
|
635
|
+
} finally {
|
|
636
|
+
client.release();
|
|
455
637
|
}
|
|
456
638
|
}
|
|
457
639
|
};
|
|
@@ -472,9 +654,13 @@ var init_mysql = __esm({
|
|
|
472
654
|
}
|
|
473
655
|
async getPool() {
|
|
474
656
|
if (!this.pool) {
|
|
475
|
-
this.pool = mysql.createPool(
|
|
657
|
+
this.pool = mysql.createPool({
|
|
658
|
+
uri: this.connection,
|
|
659
|
+
multipleStatements: true
|
|
660
|
+
});
|
|
476
661
|
this.pool.on("connection", (connection) => {
|
|
477
|
-
connection.query("SET SESSION sql_mode = 'ANSI_QUOTES'")
|
|
662
|
+
connection.query("SET SESSION sql_mode = 'ANSI_QUOTES'").catch(() => {
|
|
663
|
+
});
|
|
478
664
|
});
|
|
479
665
|
}
|
|
480
666
|
return this.pool;
|
|
@@ -485,21 +671,31 @@ var init_mysql = __esm({
|
|
|
485
671
|
async getStatus() {
|
|
486
672
|
try {
|
|
487
673
|
const pool = await this.getPool();
|
|
488
|
-
const [dbRow] = await pool.query(
|
|
674
|
+
const [dbRow] = await pool.query(
|
|
675
|
+
"SELECT DATABASE() as db, VERSION() as version"
|
|
676
|
+
);
|
|
489
677
|
const dbName = dbRow[0]?.db;
|
|
490
678
|
const version = dbRow[0]?.version;
|
|
491
|
-
const [statusRows] = await pool.query(
|
|
679
|
+
const [statusRows] = await pool.query(
|
|
680
|
+
"SHOW GLOBAL STATUS WHERE Variable_name IN ('Threads_connected', 'Queries', 'Uptime')"
|
|
681
|
+
);
|
|
492
682
|
let activeConnections = 0;
|
|
493
683
|
let queries = 0;
|
|
494
684
|
let uptime = 0;
|
|
495
685
|
for (const row of statusRows) {
|
|
496
|
-
if (row.Variable_name === "Threads_connected")
|
|
497
|
-
|
|
498
|
-
if (row.Variable_name === "
|
|
686
|
+
if (row.Variable_name === "Threads_connected")
|
|
687
|
+
activeConnections = parseInt(row.Value, 10);
|
|
688
|
+
if (row.Variable_name === "Queries")
|
|
689
|
+
queries = parseInt(row.Value, 10);
|
|
690
|
+
if (row.Variable_name === "Uptime")
|
|
691
|
+
uptime = parseInt(row.Value, 10);
|
|
499
692
|
}
|
|
500
693
|
let sizeBytes = 0;
|
|
501
694
|
if (dbName) {
|
|
502
|
-
const [sizeRow] = await pool.query(
|
|
695
|
+
const [sizeRow] = await pool.query(
|
|
696
|
+
"SELECT SUM(data_length + index_length) as size FROM information_schema.TABLES WHERE table_schema = ?",
|
|
697
|
+
[dbName]
|
|
698
|
+
);
|
|
503
699
|
sizeBytes = parseInt(sizeRow[0]?.size || "0", 10);
|
|
504
700
|
}
|
|
505
701
|
return {
|
|
@@ -521,7 +717,9 @@ var init_mysql = __esm({
|
|
|
521
717
|
}
|
|
522
718
|
async getTables() {
|
|
523
719
|
const pool = await this.getPool();
|
|
524
|
-
const [rows] = await pool.query(
|
|
720
|
+
const [rows] = await pool.query(
|
|
721
|
+
"SHOW FULL TABLES WHERE Table_type = 'BASE TABLE'"
|
|
722
|
+
);
|
|
525
723
|
return rows.map((row) => Object.values(row)[0]);
|
|
526
724
|
}
|
|
527
725
|
async getSchema(tableName) {
|
|
@@ -555,7 +753,8 @@ var init_mysql = __esm({
|
|
|
555
753
|
name: col.Field,
|
|
556
754
|
type: col.Type,
|
|
557
755
|
isPk: col.Key === "PRI",
|
|
558
|
-
nullable: col.Null === "YES",
|
|
756
|
+
nullable: col.Key === "PRI" ? false : col.Null === "YES",
|
|
757
|
+
isUnique: col.Key === "PRI" || col.Key === "UNI",
|
|
559
758
|
defaultValue: col.Default != null ? String(col.Default) : void 0,
|
|
560
759
|
enumValues,
|
|
561
760
|
fkTarget: fk ? {
|
|
@@ -567,7 +766,9 @@ var init_mysql = __esm({
|
|
|
567
766
|
}
|
|
568
767
|
async getIndexes(tableName) {
|
|
569
768
|
const pool = await this.getPool();
|
|
570
|
-
const [rows] = await pool.query(
|
|
769
|
+
const [rows] = await pool.query(
|
|
770
|
+
`SHOW INDEX FROM ${this.quoteIdentifier(tableName)}`
|
|
771
|
+
);
|
|
571
772
|
const indexMap = /* @__PURE__ */ new Map();
|
|
572
773
|
for (const row of rows) {
|
|
573
774
|
if (row.Key_name === "PRIMARY") continue;
|
|
@@ -594,7 +795,10 @@ var init_mysql = __esm({
|
|
|
594
795
|
if (orderBy) {
|
|
595
796
|
sql += ` ORDER BY ${this.quoteIdentifier(orderBy.col)} ${orderBy.asc ? "ASC" : "DESC"}`;
|
|
596
797
|
}
|
|
597
|
-
const [rows] = await pool.query(sql + " LIMIT ? OFFSET ?", [
|
|
798
|
+
const [rows] = await pool.query(sql + " LIMIT ? OFFSET ?", [
|
|
799
|
+
limit,
|
|
800
|
+
offset
|
|
801
|
+
]);
|
|
598
802
|
return { columns, rows };
|
|
599
803
|
}
|
|
600
804
|
async query(sql) {
|
|
@@ -602,16 +806,17 @@ var init_mysql = __esm({
|
|
|
602
806
|
const [rows, fields] = await pool.query(sql);
|
|
603
807
|
let columns = [];
|
|
604
808
|
let data = [];
|
|
809
|
+
let affectedRows = void 0;
|
|
605
810
|
if (fields && Array.isArray(fields)) {
|
|
606
811
|
columns = fields.map((f) => f.name);
|
|
607
812
|
data = rows;
|
|
608
813
|
} else {
|
|
609
814
|
columns = ["Result"];
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
];
|
|
815
|
+
const count = rows.affectedRows ?? 0;
|
|
816
|
+
affectedRows = count;
|
|
817
|
+
data = [{ Result: "Success", AffectedRows: count }];
|
|
613
818
|
}
|
|
614
|
-
return { columns, rows: data };
|
|
819
|
+
return { columns, rows: data, affectedRows };
|
|
615
820
|
}
|
|
616
821
|
async executeSql(sql) {
|
|
617
822
|
const pool = await this.getPool();
|
|
@@ -626,13 +831,23 @@ var init_mysql = __esm({
|
|
|
626
831
|
async insert(tableName, rows) {
|
|
627
832
|
if (rows.length === 0) return;
|
|
628
833
|
const pool = await this.getPool();
|
|
629
|
-
const
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
const
|
|
635
|
-
await
|
|
834
|
+
const connection = await pool.getConnection();
|
|
835
|
+
try {
|
|
836
|
+
const cols = Object.keys(rows[0]);
|
|
837
|
+
const colsQuoted = cols.map((c) => this.quoteIdentifier(c)).join(", ");
|
|
838
|
+
const placeholders = cols.map(() => "?").join(", ");
|
|
839
|
+
const sql = `INSERT INTO ${this.quoteIdentifier(tableName)} (${colsQuoted}) VALUES (${placeholders})`;
|
|
840
|
+
await connection.beginTransaction();
|
|
841
|
+
for (const row of rows) {
|
|
842
|
+
const values = cols.map((c) => row[c]);
|
|
843
|
+
await connection.query(sql, values);
|
|
844
|
+
}
|
|
845
|
+
await connection.commit();
|
|
846
|
+
} catch (e) {
|
|
847
|
+
await connection.rollback();
|
|
848
|
+
throw e;
|
|
849
|
+
} finally {
|
|
850
|
+
connection.release();
|
|
636
851
|
}
|
|
637
852
|
}
|
|
638
853
|
};
|
|
@@ -1034,6 +1249,335 @@ var init_export = __esm({
|
|
|
1034
1249
|
}
|
|
1035
1250
|
});
|
|
1036
1251
|
|
|
1252
|
+
// src/core/seeder.ts
|
|
1253
|
+
function getRandomItem(arr) {
|
|
1254
|
+
return arr[Math.floor(Math.random() * arr.length)];
|
|
1255
|
+
}
|
|
1256
|
+
function getRandomInt(min, max) {
|
|
1257
|
+
return Math.floor(Math.random() * (max - min + 1)) + min;
|
|
1258
|
+
}
|
|
1259
|
+
function getRandomFloat(min, max, decimals = 2) {
|
|
1260
|
+
const str = (Math.random() * (max - min) + min).toFixed(decimals);
|
|
1261
|
+
return parseFloat(str);
|
|
1262
|
+
}
|
|
1263
|
+
function getRandomDate(pastDays = 30) {
|
|
1264
|
+
const d = /* @__PURE__ */ new Date();
|
|
1265
|
+
d.setDate(d.getDate() - getRandomInt(0, pastDays));
|
|
1266
|
+
d.setHours(getRandomInt(0, 23), getRandomInt(0, 59), getRandomInt(0, 59));
|
|
1267
|
+
return d.toISOString().replace("T", " ").substring(0, 19);
|
|
1268
|
+
}
|
|
1269
|
+
function inferColumnStrategy(col) {
|
|
1270
|
+
const name = col.name.toLowerCase();
|
|
1271
|
+
const type = (col.type || "").toUpperCase();
|
|
1272
|
+
if (col.fkTarget) {
|
|
1273
|
+
return {
|
|
1274
|
+
type: "fk",
|
|
1275
|
+
label: `FK \u2192 ${col.fkTarget.table}.${col.fkTarget.column}`,
|
|
1276
|
+
target: col.fkTarget
|
|
1277
|
+
};
|
|
1278
|
+
}
|
|
1279
|
+
if (col.isPk && (type.includes("INT") || type === "SERIAL")) {
|
|
1280
|
+
return {
|
|
1281
|
+
type: "pk_auto",
|
|
1282
|
+
label: "Auto-increment (Skip / Handled by DB)"
|
|
1283
|
+
};
|
|
1284
|
+
}
|
|
1285
|
+
if (col.enumValues && col.enumValues.length > 0) {
|
|
1286
|
+
return {
|
|
1287
|
+
type: "enum",
|
|
1288
|
+
label: `Enum (${col.enumValues.slice(0, 3).join(", ")})`,
|
|
1289
|
+
values: col.enumValues
|
|
1290
|
+
};
|
|
1291
|
+
}
|
|
1292
|
+
if (type.includes("BOOL") || type === "TINYINT(1)" || name.startsWith("is_") || name.startsWith("has_")) {
|
|
1293
|
+
return { type: "boolean", label: "Boolean (true / false)" };
|
|
1294
|
+
}
|
|
1295
|
+
if (name.includes("email")) {
|
|
1296
|
+
return { type: "email", label: "Email Address" };
|
|
1297
|
+
}
|
|
1298
|
+
if (name.includes("phone") || name.includes("tel") || name.includes("mobile")) {
|
|
1299
|
+
return { type: "phone", label: "Phone Number" };
|
|
1300
|
+
}
|
|
1301
|
+
if (name.includes("first_name")) {
|
|
1302
|
+
return { type: "first_name", label: "First Name" };
|
|
1303
|
+
}
|
|
1304
|
+
if (name.includes("last_name")) {
|
|
1305
|
+
return { type: "last_name", label: "Last Name" };
|
|
1306
|
+
}
|
|
1307
|
+
if (name.includes("name") || name.includes("author") || name.includes("user")) {
|
|
1308
|
+
return { type: "full_name", label: "Full Name" };
|
|
1309
|
+
}
|
|
1310
|
+
if (name.includes("avatar") || name.includes("image") || name.includes("photo") || name.includes("thumbnail")) {
|
|
1311
|
+
return { type: "avatar", label: "Avatar / Image URL" };
|
|
1312
|
+
}
|
|
1313
|
+
if (name.includes("city")) {
|
|
1314
|
+
return { type: "city", label: "City Name" };
|
|
1315
|
+
}
|
|
1316
|
+
if (name.includes("country")) {
|
|
1317
|
+
return { type: "country", label: "Country Name" };
|
|
1318
|
+
}
|
|
1319
|
+
if (name.includes("address")) {
|
|
1320
|
+
return { type: "address", label: "Street Address" };
|
|
1321
|
+
}
|
|
1322
|
+
if (name.includes("title") || name.includes("subject") || name.includes("headline")) {
|
|
1323
|
+
return { type: "title", label: "Title / Headline" };
|
|
1324
|
+
}
|
|
1325
|
+
if (name.includes("desc") || name.includes("content") || name.includes("body") || name.includes("bio") || name.includes("comment") || type.includes("TEXT")) {
|
|
1326
|
+
return { type: "paragraph", label: "Text / Paragraph" };
|
|
1327
|
+
}
|
|
1328
|
+
if (name.includes("status")) {
|
|
1329
|
+
return { type: "status", label: "Status (active/pending...)" };
|
|
1330
|
+
}
|
|
1331
|
+
if (name.includes("role")) {
|
|
1332
|
+
return { type: "role", label: "Role (admin/member...)" };
|
|
1333
|
+
}
|
|
1334
|
+
if (name.includes("price") || name.includes("amount") || name.includes("cost") || name.includes("salary") || type.includes("DECIMAL") || type.includes("FLOAT") || type.includes("REAL") || type.includes("NUMERIC")) {
|
|
1335
|
+
return { type: "price", label: "Price / Currency" };
|
|
1336
|
+
}
|
|
1337
|
+
if (type.includes("DATE") || type.includes("TIME")) {
|
|
1338
|
+
return { type: "date", label: "Date / Timestamp" };
|
|
1339
|
+
}
|
|
1340
|
+
if (type.includes("INT") || name.includes("age") || name.includes("count") || name.includes("qty") || name.includes("quantity")) {
|
|
1341
|
+
return { type: "integer", label: "Integer (1 - 500)" };
|
|
1342
|
+
}
|
|
1343
|
+
if (type.includes("UUID")) {
|
|
1344
|
+
return { type: "uuid", label: "UUID v4" };
|
|
1345
|
+
}
|
|
1346
|
+
return { type: "string", label: "Random Word / Code" };
|
|
1347
|
+
}
|
|
1348
|
+
function generateFieldValue(strategy, fkCache = {}) {
|
|
1349
|
+
switch (strategy.type) {
|
|
1350
|
+
case "pk_auto":
|
|
1351
|
+
return void 0;
|
|
1352
|
+
// Don't include in insert
|
|
1353
|
+
case "fk": {
|
|
1354
|
+
if (!strategy.target) return 1;
|
|
1355
|
+
const { table, column } = strategy.target;
|
|
1356
|
+
const ids = fkCache[`${table}.${column}`] || [];
|
|
1357
|
+
if (ids.length > 0) {
|
|
1358
|
+
return getRandomItem(ids);
|
|
1359
|
+
}
|
|
1360
|
+
return 1;
|
|
1361
|
+
}
|
|
1362
|
+
case "enum":
|
|
1363
|
+
return strategy.values && strategy.values.length > 0 ? getRandomItem(strategy.values) : "Default";
|
|
1364
|
+
case "boolean":
|
|
1365
|
+
return Math.random() > 0.5 ? 1 : 0;
|
|
1366
|
+
case "email": {
|
|
1367
|
+
const f = getRandomItem(FIRST_NAMES).toLowerCase();
|
|
1368
|
+
const l = getRandomItem(LAST_NAMES).toLowerCase();
|
|
1369
|
+
return `${f}.${l}${getRandomInt(1, 99)}@${getRandomItem(DOMAINS)}`;
|
|
1370
|
+
}
|
|
1371
|
+
case "phone":
|
|
1372
|
+
return `+1 (${getRandomInt(200, 999)}) ${getRandomInt(200, 999)}-${getRandomInt(1e3, 9999)}`;
|
|
1373
|
+
case "first_name":
|
|
1374
|
+
return getRandomItem(FIRST_NAMES);
|
|
1375
|
+
case "last_name":
|
|
1376
|
+
return getRandomItem(LAST_NAMES);
|
|
1377
|
+
case "full_name":
|
|
1378
|
+
return `${getRandomItem(FIRST_NAMES)} ${getRandomItem(LAST_NAMES)}`;
|
|
1379
|
+
case "avatar":
|
|
1380
|
+
return `https://picsum.photos/seed/${getRandomInt(100, 9999)}/200`;
|
|
1381
|
+
case "city":
|
|
1382
|
+
return getRandomItem(CITIES);
|
|
1383
|
+
case "country":
|
|
1384
|
+
return getRandomItem([
|
|
1385
|
+
"United States",
|
|
1386
|
+
"United Kingdom",
|
|
1387
|
+
"Germany",
|
|
1388
|
+
"Japan",
|
|
1389
|
+
"Canada",
|
|
1390
|
+
"Singapore"
|
|
1391
|
+
]);
|
|
1392
|
+
case "address":
|
|
1393
|
+
return `${getRandomInt(10, 999)} ${getRandomItem(["Market St", "Broadway", "Highland Ave", "Pine Rd", "Maple St"])}`;
|
|
1394
|
+
case "title":
|
|
1395
|
+
return getRandomItem(TITLES);
|
|
1396
|
+
case "paragraph":
|
|
1397
|
+
return getRandomItem(PARAGRAPHS);
|
|
1398
|
+
case "status":
|
|
1399
|
+
return getRandomItem(STATUSES);
|
|
1400
|
+
case "role":
|
|
1401
|
+
return getRandomItem(ROLES);
|
|
1402
|
+
case "price":
|
|
1403
|
+
return getRandomFloat(9.99, 499.99);
|
|
1404
|
+
case "date":
|
|
1405
|
+
return getRandomDate(30);
|
|
1406
|
+
case "integer":
|
|
1407
|
+
return getRandomInt(1, 500);
|
|
1408
|
+
case "uuid":
|
|
1409
|
+
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
|
|
1410
|
+
const r = Math.random() * 16 | 0;
|
|
1411
|
+
const v = c === "x" ? r : r & 3 | 8;
|
|
1412
|
+
return v.toString(16);
|
|
1413
|
+
});
|
|
1414
|
+
default:
|
|
1415
|
+
return `Item_${getRandomInt(100, 9999)}`;
|
|
1416
|
+
}
|
|
1417
|
+
}
|
|
1418
|
+
async function getFkCandidates(adapter, schema) {
|
|
1419
|
+
const fkCache = {};
|
|
1420
|
+
for (const col of schema) {
|
|
1421
|
+
if (col.fkTarget) {
|
|
1422
|
+
const key = `${col.fkTarget.table}.${col.fkTarget.column}`;
|
|
1423
|
+
if (!fkCache[key]) {
|
|
1424
|
+
try {
|
|
1425
|
+
const data = await adapter.getData(col.fkTarget.table, 50);
|
|
1426
|
+
if (data && data.rows && data.rows.length > 0) {
|
|
1427
|
+
fkCache[key] = data.rows.map((r) => r[col.fkTarget.column]).filter((v) => v !== null && v !== void 0);
|
|
1428
|
+
}
|
|
1429
|
+
} catch {
|
|
1430
|
+
fkCache[key] = [];
|
|
1431
|
+
}
|
|
1432
|
+
}
|
|
1433
|
+
}
|
|
1434
|
+
}
|
|
1435
|
+
return fkCache;
|
|
1436
|
+
}
|
|
1437
|
+
async function previewMockData(adapter, tableName, previewCount = 3) {
|
|
1438
|
+
const schema = await adapter.getSchema(tableName);
|
|
1439
|
+
const fkCache = await getFkCandidates(adapter, schema);
|
|
1440
|
+
const rules = schema.map((col) => ({
|
|
1441
|
+
column: col,
|
|
1442
|
+
strategy: inferColumnStrategy(col)
|
|
1443
|
+
}));
|
|
1444
|
+
const previewRows = [];
|
|
1445
|
+
for (let i = 0; i < previewCount; i++) {
|
|
1446
|
+
const row = {};
|
|
1447
|
+
for (const { column, strategy } of rules) {
|
|
1448
|
+
const val = generateFieldValue(strategy, fkCache);
|
|
1449
|
+
row[column.name] = val !== void 0 ? val : "(auto)";
|
|
1450
|
+
}
|
|
1451
|
+
previewRows.push(row);
|
|
1452
|
+
}
|
|
1453
|
+
return {
|
|
1454
|
+
tableName,
|
|
1455
|
+
rules,
|
|
1456
|
+
previewRows
|
|
1457
|
+
};
|
|
1458
|
+
}
|
|
1459
|
+
async function generateAndInsertMockData(adapter, tableName, count, onProgress) {
|
|
1460
|
+
const schema = await adapter.getSchema(tableName);
|
|
1461
|
+
const fkCache = await getFkCandidates(adapter, schema);
|
|
1462
|
+
const rules = schema.map((col) => ({
|
|
1463
|
+
column: col,
|
|
1464
|
+
strategy: inferColumnStrategy(col)
|
|
1465
|
+
}));
|
|
1466
|
+
const activeRules = rules.filter((r) => r.strategy.type !== "pk_auto");
|
|
1467
|
+
if (activeRules.length === 0) {
|
|
1468
|
+
return 0;
|
|
1469
|
+
}
|
|
1470
|
+
const rowsToInsert = [];
|
|
1471
|
+
for (let i = 0; i < count; i++) {
|
|
1472
|
+
const row = {};
|
|
1473
|
+
for (const { column, strategy } of activeRules) {
|
|
1474
|
+
const val = generateFieldValue(strategy, fkCache);
|
|
1475
|
+
if (val !== void 0) {
|
|
1476
|
+
row[column.name] = val;
|
|
1477
|
+
}
|
|
1478
|
+
}
|
|
1479
|
+
rowsToInsert.push(row);
|
|
1480
|
+
}
|
|
1481
|
+
const CHUNK_SIZE = 250;
|
|
1482
|
+
let inserted = 0;
|
|
1483
|
+
for (let i = 0; i < rowsToInsert.length; i += CHUNK_SIZE) {
|
|
1484
|
+
const chunk = rowsToInsert.slice(i, i + CHUNK_SIZE);
|
|
1485
|
+
await adapter.insert(tableName, chunk);
|
|
1486
|
+
inserted += chunk.length;
|
|
1487
|
+
if (onProgress) {
|
|
1488
|
+
onProgress(inserted, count);
|
|
1489
|
+
}
|
|
1490
|
+
}
|
|
1491
|
+
return inserted;
|
|
1492
|
+
}
|
|
1493
|
+
var FIRST_NAMES, LAST_NAMES, DOMAINS, CITIES, TITLES, PARAGRAPHS, STATUSES, ROLES;
|
|
1494
|
+
var init_seeder = __esm({
|
|
1495
|
+
"src/core/seeder.ts"() {
|
|
1496
|
+
"use strict";
|
|
1497
|
+
FIRST_NAMES = [
|
|
1498
|
+
"James",
|
|
1499
|
+
"Mary",
|
|
1500
|
+
"John",
|
|
1501
|
+
"Patricia",
|
|
1502
|
+
"Robert",
|
|
1503
|
+
"Jennifer",
|
|
1504
|
+
"Michael",
|
|
1505
|
+
"Linda",
|
|
1506
|
+
"William",
|
|
1507
|
+
"Elizabeth",
|
|
1508
|
+
"David",
|
|
1509
|
+
"Barbara",
|
|
1510
|
+
"Richard",
|
|
1511
|
+
"Susan",
|
|
1512
|
+
"Joseph",
|
|
1513
|
+
"Jessica",
|
|
1514
|
+
"Thomas",
|
|
1515
|
+
"Sarah",
|
|
1516
|
+
"Charles",
|
|
1517
|
+
"Karen",
|
|
1518
|
+
"Lucas",
|
|
1519
|
+
"Emma",
|
|
1520
|
+
"Alex",
|
|
1521
|
+
"Olivia"
|
|
1522
|
+
];
|
|
1523
|
+
LAST_NAMES = [
|
|
1524
|
+
"Smith",
|
|
1525
|
+
"Johnson",
|
|
1526
|
+
"Williams",
|
|
1527
|
+
"Brown",
|
|
1528
|
+
"Jones",
|
|
1529
|
+
"Garcia",
|
|
1530
|
+
"Miller",
|
|
1531
|
+
"Davis",
|
|
1532
|
+
"Rodriguez",
|
|
1533
|
+
"Martinez",
|
|
1534
|
+
"Hernandez",
|
|
1535
|
+
"Lopez",
|
|
1536
|
+
"Gonzalez",
|
|
1537
|
+
"Wilson",
|
|
1538
|
+
"Anderson",
|
|
1539
|
+
"Taylor"
|
|
1540
|
+
];
|
|
1541
|
+
DOMAINS = [
|
|
1542
|
+
"gmail.com",
|
|
1543
|
+
"outlook.com",
|
|
1544
|
+
"yahoo.com",
|
|
1545
|
+
"example.com",
|
|
1546
|
+
"company.io"
|
|
1547
|
+
];
|
|
1548
|
+
CITIES = [
|
|
1549
|
+
"New York",
|
|
1550
|
+
"San Francisco",
|
|
1551
|
+
"London",
|
|
1552
|
+
"Tokyo",
|
|
1553
|
+
"Berlin",
|
|
1554
|
+
"Paris",
|
|
1555
|
+
"Sydney",
|
|
1556
|
+
"Toronto",
|
|
1557
|
+
"Singapore",
|
|
1558
|
+
"Amsterdam"
|
|
1559
|
+
];
|
|
1560
|
+
TITLES = [
|
|
1561
|
+
"Getting started with database design",
|
|
1562
|
+
"10 tips for clean and maintainable code",
|
|
1563
|
+
"Understanding indexing and performance",
|
|
1564
|
+
"How to optimize complex SQL queries",
|
|
1565
|
+
"Modern web application architecture",
|
|
1566
|
+
"Best practices for database migrations",
|
|
1567
|
+
"A deep dive into relational models",
|
|
1568
|
+
"Improving frontend latency and responsiveness"
|
|
1569
|
+
];
|
|
1570
|
+
PARAGRAPHS = [
|
|
1571
|
+
"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.",
|
|
1572
|
+
"Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.",
|
|
1573
|
+
"Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.",
|
|
1574
|
+
"Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
|
|
1575
|
+
];
|
|
1576
|
+
STATUSES = ["active", "pending", "completed", "inactive", "archived"];
|
|
1577
|
+
ROLES = ["admin", "editor", "member", "viewer", "moderator"];
|
|
1578
|
+
}
|
|
1579
|
+
});
|
|
1580
|
+
|
|
1037
1581
|
// src/server/api.ts
|
|
1038
1582
|
import { Hono } from "hono";
|
|
1039
1583
|
import { spawn } from "child_process";
|
|
@@ -1111,7 +1655,9 @@ function registerApiRoutes(app, dbConfig) {
|
|
|
1111
1655
|
const stats = {};
|
|
1112
1656
|
for (const t of tables) {
|
|
1113
1657
|
try {
|
|
1114
|
-
const res = await adapter.query(
|
|
1658
|
+
const res = await adapter.query(
|
|
1659
|
+
`SELECT COUNT(*) as c FROM ${adapter.quoteIdentifier(t)}`
|
|
1660
|
+
);
|
|
1115
1661
|
if (res && res.rows && res.rows.length > 0) {
|
|
1116
1662
|
const row = res.rows[0];
|
|
1117
1663
|
const countVal = Object.values(row)[0];
|
|
@@ -1137,6 +1683,33 @@ function registerApiRoutes(app, dbConfig) {
|
|
|
1137
1683
|
return c.json({ success: false, error: e.message }, 500);
|
|
1138
1684
|
}
|
|
1139
1685
|
});
|
|
1686
|
+
api.post("/tables/:name/schema", async (c) => {
|
|
1687
|
+
const tableName = c.req.param("name");
|
|
1688
|
+
try {
|
|
1689
|
+
const body = await c.req.json().catch(() => ({}));
|
|
1690
|
+
const { columns, renames } = body;
|
|
1691
|
+
if (!columns || !Array.isArray(columns)) {
|
|
1692
|
+
return c.json(
|
|
1693
|
+
{ success: false, error: "Invalid columns parameter" },
|
|
1694
|
+
400
|
|
1695
|
+
);
|
|
1696
|
+
}
|
|
1697
|
+
if (adapter.recreateTable) {
|
|
1698
|
+
await adapter.recreateTable(tableName, columns, renames || {});
|
|
1699
|
+
return c.json({ success: true });
|
|
1700
|
+
} else {
|
|
1701
|
+
return c.json(
|
|
1702
|
+
{
|
|
1703
|
+
success: false,
|
|
1704
|
+
error: "Table recreation is currently supported for SQLite"
|
|
1705
|
+
},
|
|
1706
|
+
400
|
|
1707
|
+
);
|
|
1708
|
+
}
|
|
1709
|
+
} catch (e) {
|
|
1710
|
+
return c.json({ success: false, error: e.message }, 500);
|
|
1711
|
+
}
|
|
1712
|
+
});
|
|
1140
1713
|
api.get("/tables/:name/indexes", async (c) => {
|
|
1141
1714
|
const tableName = c.req.param("name");
|
|
1142
1715
|
try {
|
|
@@ -1158,12 +1731,42 @@ function registerApiRoutes(app, dbConfig) {
|
|
|
1158
1731
|
orderBy = { col: orderCol, asc: orderAscStr !== "false" };
|
|
1159
1732
|
}
|
|
1160
1733
|
try {
|
|
1161
|
-
const data = await adapter.getData(
|
|
1734
|
+
const data = await adapter.getData(
|
|
1735
|
+
tableName,
|
|
1736
|
+
limit,
|
|
1737
|
+
offset,
|
|
1738
|
+
whereClause,
|
|
1739
|
+
orderBy
|
|
1740
|
+
);
|
|
1162
1741
|
return c.json({ success: true, data });
|
|
1163
1742
|
} catch (e) {
|
|
1164
1743
|
return c.json({ success: false, error: e.message }, 500);
|
|
1165
1744
|
}
|
|
1166
1745
|
});
|
|
1746
|
+
api.get("/tables/:name/mock/preview", async (c) => {
|
|
1747
|
+
try {
|
|
1748
|
+
const tableName = c.req.param("name");
|
|
1749
|
+
const data = await previewMockData(adapter, tableName, 3);
|
|
1750
|
+
return c.json({ success: true, data });
|
|
1751
|
+
} catch (e) {
|
|
1752
|
+
return c.json({ success: false, error: e.message }, 500);
|
|
1753
|
+
}
|
|
1754
|
+
});
|
|
1755
|
+
api.post("/tables/:name/mock", async (c) => {
|
|
1756
|
+
try {
|
|
1757
|
+
const tableName = c.req.param("name");
|
|
1758
|
+
const body = await c.req.json().catch(() => ({}));
|
|
1759
|
+
const count = Math.min(Math.max(Number(body.count) || 10, 1), 1e3);
|
|
1760
|
+
const inserted = await generateAndInsertMockData(
|
|
1761
|
+
adapter,
|
|
1762
|
+
tableName,
|
|
1763
|
+
count
|
|
1764
|
+
);
|
|
1765
|
+
return c.json({ success: true, count: inserted });
|
|
1766
|
+
} catch (e) {
|
|
1767
|
+
return c.json({ success: false, error: e.message }, 500);
|
|
1768
|
+
}
|
|
1769
|
+
});
|
|
1167
1770
|
api.post("/query", async (c) => {
|
|
1168
1771
|
try {
|
|
1169
1772
|
const { sql } = await c.req.json();
|
|
@@ -1173,34 +1776,26 @@ function registerApiRoutes(app, dbConfig) {
|
|
|
1173
1776
|
return c.json({ success: false, error: e.message }, 500);
|
|
1174
1777
|
}
|
|
1175
1778
|
});
|
|
1176
|
-
api.
|
|
1177
|
-
const tableName = c.req.param("name");
|
|
1178
|
-
const format = c.req.query("format") || "csv";
|
|
1179
|
-
const whereClause = c.req.query("where") || "";
|
|
1180
|
-
const orderCol = c.req.query("orderCol");
|
|
1181
|
-
const orderAscStr = c.req.query("orderAsc");
|
|
1182
|
-
let orderBy = void 0;
|
|
1183
|
-
if (orderCol) {
|
|
1184
|
-
orderBy = { col: orderCol, asc: orderAscStr !== "false" };
|
|
1185
|
-
}
|
|
1779
|
+
api.post("/query/export", async (c) => {
|
|
1186
1780
|
try {
|
|
1187
|
-
const
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
batchOffset += batchSize;
|
|
1197
|
-
} while (lastBatch.rows.length === batchSize);
|
|
1198
|
-
const rows = allRows;
|
|
1781
|
+
const { sql } = await c.req.json();
|
|
1782
|
+
if (!sql) {
|
|
1783
|
+
return c.json(
|
|
1784
|
+
{ success: false, error: "No SQL query provided" },
|
|
1785
|
+
400
|
|
1786
|
+
);
|
|
1787
|
+
}
|
|
1788
|
+
const result = await adapter.query(sql);
|
|
1789
|
+
const rows = result.rows;
|
|
1199
1790
|
const timestamp = getDatetimeStr();
|
|
1200
|
-
const
|
|
1791
|
+
const format = c.req.query("format") || "csv";
|
|
1792
|
+
const exportFilename = `query_result_${timestamp}`;
|
|
1201
1793
|
if (format === "json") {
|
|
1202
1794
|
const jsonStr = JSON.stringify(rows, null, 2);
|
|
1203
|
-
c.header(
|
|
1795
|
+
c.header(
|
|
1796
|
+
"Content-Disposition",
|
|
1797
|
+
`attachment; filename="${exportFilename}.json"`
|
|
1798
|
+
);
|
|
1204
1799
|
c.header("Content-Type", "application/json");
|
|
1205
1800
|
return c.body(jsonStr);
|
|
1206
1801
|
} else {
|
|
@@ -1221,10 +1816,106 @@ function registerApiRoutes(app, dbConfig) {
|
|
|
1221
1816
|
});
|
|
1222
1817
|
csvStr = [headerStr, ...rowStrs].join("\n");
|
|
1223
1818
|
}
|
|
1224
|
-
c.header(
|
|
1819
|
+
c.header(
|
|
1820
|
+
"Content-Disposition",
|
|
1821
|
+
`attachment; filename="${exportFilename}.csv"`
|
|
1822
|
+
);
|
|
1225
1823
|
c.header("Content-Type", "text/csv");
|
|
1226
1824
|
return c.body(csvStr);
|
|
1227
1825
|
}
|
|
1826
|
+
} catch (e) {
|
|
1827
|
+
return c.json({ success: false, error: e.message }, 500);
|
|
1828
|
+
}
|
|
1829
|
+
});
|
|
1830
|
+
api.get("/tables/:name/export", async (c) => {
|
|
1831
|
+
const tableName = c.req.param("name");
|
|
1832
|
+
const format = c.req.query("format") || "csv";
|
|
1833
|
+
const whereClause = c.req.query("where") || "";
|
|
1834
|
+
const orderCol = c.req.query("orderCol");
|
|
1835
|
+
const orderAscStr = c.req.query("orderAsc");
|
|
1836
|
+
let orderBy = void 0;
|
|
1837
|
+
if (orderCol) {
|
|
1838
|
+
orderBy = { col: orderCol, asc: orderAscStr !== "false" };
|
|
1839
|
+
}
|
|
1840
|
+
try {
|
|
1841
|
+
const timestamp = getDatetimeStr();
|
|
1842
|
+
const exportFilename = `${tableName}_export_${timestamp}`;
|
|
1843
|
+
const stream = new ReadableStream({
|
|
1844
|
+
async start(controller) {
|
|
1845
|
+
const encoder = new TextEncoder();
|
|
1846
|
+
try {
|
|
1847
|
+
const batchSize = 1e3;
|
|
1848
|
+
let batchOffset = 0;
|
|
1849
|
+
let hasMore = true;
|
|
1850
|
+
let isFirstChunk = true;
|
|
1851
|
+
if (format === "json") {
|
|
1852
|
+
controller.enqueue(encoder.encode("[\n"));
|
|
1853
|
+
}
|
|
1854
|
+
while (hasMore) {
|
|
1855
|
+
const batch = await adapter.getData(
|
|
1856
|
+
tableName,
|
|
1857
|
+
batchSize,
|
|
1858
|
+
batchOffset,
|
|
1859
|
+
whereClause,
|
|
1860
|
+
orderBy
|
|
1861
|
+
);
|
|
1862
|
+
if (batch.rows.length === 0) {
|
|
1863
|
+
break;
|
|
1864
|
+
}
|
|
1865
|
+
if (format === "json") {
|
|
1866
|
+
let jsonStr = "";
|
|
1867
|
+
for (let i = 0; i < batch.rows.length; i++) {
|
|
1868
|
+
if (!isFirstChunk || i > 0) {
|
|
1869
|
+
jsonStr += ",\n";
|
|
1870
|
+
}
|
|
1871
|
+
jsonStr += JSON.stringify(batch.rows[i], null, 2);
|
|
1872
|
+
}
|
|
1873
|
+
controller.enqueue(encoder.encode(jsonStr));
|
|
1874
|
+
} else {
|
|
1875
|
+
let csvStr = "";
|
|
1876
|
+
const headers = batch.columns || Object.keys(batch.rows[0] || {});
|
|
1877
|
+
if (isFirstChunk && headers.length > 0) {
|
|
1878
|
+
csvStr += headers.join(",") + "\n";
|
|
1879
|
+
}
|
|
1880
|
+
for (const r of batch.rows) {
|
|
1881
|
+
const rowStr = headers.map((h) => {
|
|
1882
|
+
let val = r[h];
|
|
1883
|
+
if (val === null || val === void 0)
|
|
1884
|
+
val = "";
|
|
1885
|
+
val = String(val);
|
|
1886
|
+
if (val.includes(",") || val.includes('"') || val.includes("\n")) {
|
|
1887
|
+
val = `"${val.replace(/"/g, '""')}"`;
|
|
1888
|
+
}
|
|
1889
|
+
return val;
|
|
1890
|
+
}).join(",");
|
|
1891
|
+
csvStr += rowStr + "\n";
|
|
1892
|
+
}
|
|
1893
|
+
controller.enqueue(encoder.encode(csvStr));
|
|
1894
|
+
}
|
|
1895
|
+
isFirstChunk = false;
|
|
1896
|
+
batchOffset += batchSize;
|
|
1897
|
+
if (batch.rows.length < batchSize) {
|
|
1898
|
+
hasMore = false;
|
|
1899
|
+
}
|
|
1900
|
+
}
|
|
1901
|
+
if (format === "json") {
|
|
1902
|
+
controller.enqueue(encoder.encode("\n]"));
|
|
1903
|
+
}
|
|
1904
|
+
controller.close();
|
|
1905
|
+
} catch (e) {
|
|
1906
|
+
controller.error(e);
|
|
1907
|
+
}
|
|
1908
|
+
}
|
|
1909
|
+
});
|
|
1910
|
+
c.header(
|
|
1911
|
+
"Content-Disposition",
|
|
1912
|
+
`attachment; filename="${exportFilename}.${format}"`
|
|
1913
|
+
);
|
|
1914
|
+
c.header(
|
|
1915
|
+
"Content-Type",
|
|
1916
|
+
format === "json" ? "application/json" : "text/csv"
|
|
1917
|
+
);
|
|
1918
|
+
return c.body(stream);
|
|
1228
1919
|
} catch (e) {
|
|
1229
1920
|
return c.text(`Export Failed: ${e.message}`, 500);
|
|
1230
1921
|
}
|
|
@@ -1239,13 +1930,19 @@ function registerApiRoutes(app, dbConfig) {
|
|
|
1239
1930
|
return new Promise((resolve, reject) => {
|
|
1240
1931
|
const cp = spawn(cmd, args);
|
|
1241
1932
|
let started = false;
|
|
1933
|
+
const stream = nodeToWebStream(cp.stdout);
|
|
1242
1934
|
cp.on("error", (err) => {
|
|
1243
|
-
if (!started)
|
|
1935
|
+
if (!started)
|
|
1936
|
+
reject(
|
|
1937
|
+
new Error(
|
|
1938
|
+
`Native tool '${cmd}' not found. Please install ${envName}.`
|
|
1939
|
+
)
|
|
1940
|
+
);
|
|
1244
1941
|
});
|
|
1245
1942
|
setTimeout(() => {
|
|
1246
1943
|
if (!cp.killed) {
|
|
1247
1944
|
started = true;
|
|
1248
|
-
resolve(
|
|
1945
|
+
resolve(stream);
|
|
1249
1946
|
}
|
|
1250
1947
|
}, 100);
|
|
1251
1948
|
});
|
|
@@ -1257,7 +1954,11 @@ function registerApiRoutes(app, dbConfig) {
|
|
|
1257
1954
|
filename = `${baseName}_backup_${timeStr}.sql`;
|
|
1258
1955
|
if (type === "sqlite") {
|
|
1259
1956
|
const dbPath = url.replace("file:", "");
|
|
1260
|
-
stream = await executeNativeDump(
|
|
1957
|
+
stream = await executeNativeDump(
|
|
1958
|
+
"sqlite3",
|
|
1959
|
+
[dbPath, ".dump"],
|
|
1960
|
+
"SQLite CLI"
|
|
1961
|
+
);
|
|
1261
1962
|
} else if (type === "mysql") {
|
|
1262
1963
|
const parsed = new URL(url);
|
|
1263
1964
|
const user = parsed.username;
|
|
@@ -1265,23 +1966,44 @@ function registerApiRoutes(app, dbConfig) {
|
|
|
1265
1966
|
const host = parsed.hostname;
|
|
1266
1967
|
const port = parsed.port || "3306";
|
|
1267
1968
|
const dbname = parsed.pathname.substring(1);
|
|
1268
|
-
stream = await executeNativeDump(
|
|
1969
|
+
stream = await executeNativeDump(
|
|
1970
|
+
"mysqldump",
|
|
1971
|
+
["-u", user, `-p${pass}`, "-h", host, "-P", port, dbname],
|
|
1972
|
+
"MySQL Client"
|
|
1973
|
+
);
|
|
1269
1974
|
} else if (type === "postgres") {
|
|
1270
|
-
stream = await executeNativeDump(
|
|
1975
|
+
stream = await executeNativeDump(
|
|
1976
|
+
"pg_dump",
|
|
1977
|
+
[url],
|
|
1978
|
+
"PostgreSQL CLI"
|
|
1979
|
+
);
|
|
1271
1980
|
} else {
|
|
1272
1981
|
throw new Error("Unsupported database type for native export");
|
|
1273
1982
|
}
|
|
1274
|
-
c.header(
|
|
1983
|
+
c.header(
|
|
1984
|
+
"Content-Disposition",
|
|
1985
|
+
`attachment; filename="${filename}"`
|
|
1986
|
+
);
|
|
1275
1987
|
c.header("Content-Type", "application/sql");
|
|
1276
1988
|
return c.body(stream);
|
|
1277
1989
|
} catch (err) {
|
|
1278
|
-
if (
|
|
1279
|
-
|
|
1990
|
+
if (err.message.includes("Native tool")) {
|
|
1991
|
+
let fallbackStream;
|
|
1280
1992
|
const baseName = getDbName();
|
|
1281
1993
|
const timeStr = getDatetimeStr();
|
|
1282
|
-
|
|
1994
|
+
if (type === "sqlite") {
|
|
1995
|
+
fallbackStream = generateSqliteDumpStream(adapter);
|
|
1996
|
+
} else if (type === "mysql") {
|
|
1997
|
+
fallbackStream = generateMysqlDumpStream(adapter);
|
|
1998
|
+
} else {
|
|
1999
|
+
throw err;
|
|
2000
|
+
}
|
|
2001
|
+
c.header(
|
|
2002
|
+
"Content-Disposition",
|
|
2003
|
+
`attachment; filename="${baseName}_backup_${timeStr}.sql"`
|
|
2004
|
+
);
|
|
1283
2005
|
c.header("Content-Type", "application/sql");
|
|
1284
|
-
return c.body(
|
|
2006
|
+
return c.body(fallbackStream);
|
|
1285
2007
|
}
|
|
1286
2008
|
throw err;
|
|
1287
2009
|
}
|
|
@@ -1291,6 +2013,89 @@ function registerApiRoutes(app, dbConfig) {
|
|
|
1291
2013
|
return c.body(errorHtml);
|
|
1292
2014
|
}
|
|
1293
2015
|
});
|
|
2016
|
+
api.post("/database/import", async (c) => {
|
|
2017
|
+
try {
|
|
2018
|
+
const body = await c.req.parseBody();
|
|
2019
|
+
const file = body["file"];
|
|
2020
|
+
if (!(file instanceof File)) {
|
|
2021
|
+
return c.json({ success: false, error: "No file uploaded" }, 400);
|
|
2022
|
+
}
|
|
2023
|
+
const sqlContent = await file.text();
|
|
2024
|
+
const type = dbConfig.type;
|
|
2025
|
+
const url = dbConfig.targetUrl;
|
|
2026
|
+
const executeNativeImport = (cmd, args, fileContent, envName) => {
|
|
2027
|
+
return new Promise((resolve, reject) => {
|
|
2028
|
+
const cp = spawn(cmd, args);
|
|
2029
|
+
let started = false;
|
|
2030
|
+
let errStr = "";
|
|
2031
|
+
cp.on("error", (err) => {
|
|
2032
|
+
if (!started)
|
|
2033
|
+
reject(
|
|
2034
|
+
new Error(
|
|
2035
|
+
`Native tool '${cmd}' not found. Please install ${envName}.`
|
|
2036
|
+
)
|
|
2037
|
+
);
|
|
2038
|
+
});
|
|
2039
|
+
cp.stderr.on("data", (d) => errStr += d.toString());
|
|
2040
|
+
cp.on("close", (code) => {
|
|
2041
|
+
if (code === 0) resolve();
|
|
2042
|
+
else reject(new Error(`Native import failed: ${errStr}`));
|
|
2043
|
+
});
|
|
2044
|
+
cp.stdin.write(fileContent);
|
|
2045
|
+
cp.stdin.end();
|
|
2046
|
+
started = true;
|
|
2047
|
+
});
|
|
2048
|
+
};
|
|
2049
|
+
try {
|
|
2050
|
+
if (type === "sqlite") {
|
|
2051
|
+
const dbPath = url.replace("file:", "");
|
|
2052
|
+
await executeNativeImport(
|
|
2053
|
+
"sqlite3",
|
|
2054
|
+
[dbPath],
|
|
2055
|
+
sqlContent,
|
|
2056
|
+
"SQLite CLI"
|
|
2057
|
+
);
|
|
2058
|
+
} else if (type === "mysql") {
|
|
2059
|
+
const parsed = new URL(url);
|
|
2060
|
+
const user = parsed.username;
|
|
2061
|
+
const pass = parsed.password;
|
|
2062
|
+
const host = parsed.hostname;
|
|
2063
|
+
const port = parsed.port || "3306";
|
|
2064
|
+
const dbname = parsed.pathname.substring(1);
|
|
2065
|
+
await executeNativeImport(
|
|
2066
|
+
"mysql",
|
|
2067
|
+
["-u", user, `-p${pass}`, "-h", host, "-P", port, dbname],
|
|
2068
|
+
sqlContent,
|
|
2069
|
+
"MySQL Client"
|
|
2070
|
+
);
|
|
2071
|
+
} else if (type === "postgres") {
|
|
2072
|
+
await executeNativeImport(
|
|
2073
|
+
"psql",
|
|
2074
|
+
[url],
|
|
2075
|
+
sqlContent,
|
|
2076
|
+
"PostgreSQL CLI"
|
|
2077
|
+
);
|
|
2078
|
+
} else {
|
|
2079
|
+
throw new Error("Unsupported database type for native import");
|
|
2080
|
+
}
|
|
2081
|
+
} catch (err) {
|
|
2082
|
+
if (err.message.includes("Native tool")) {
|
|
2083
|
+
await adapter.executeSql(sqlContent);
|
|
2084
|
+
} else {
|
|
2085
|
+
throw err;
|
|
2086
|
+
}
|
|
2087
|
+
}
|
|
2088
|
+
return c.json({
|
|
2089
|
+
success: true,
|
|
2090
|
+
message: "Import completed successfully"
|
|
2091
|
+
});
|
|
2092
|
+
} catch (e) {
|
|
2093
|
+
return c.json(
|
|
2094
|
+
{ success: false, error: `Import Failed: ${e.message}` },
|
|
2095
|
+
500
|
|
2096
|
+
);
|
|
2097
|
+
}
|
|
2098
|
+
});
|
|
1294
2099
|
api.get("/database/dictionary", async (c) => {
|
|
1295
2100
|
try {
|
|
1296
2101
|
const dbName = getDbName();
|
|
@@ -1313,7 +2118,10 @@ function registerApiRoutes(app, dbConfig) {
|
|
|
1313
2118
|
md += `
|
|
1314
2119
|
`;
|
|
1315
2120
|
}
|
|
1316
|
-
c.header(
|
|
2121
|
+
c.header(
|
|
2122
|
+
"Content-Disposition",
|
|
2123
|
+
`attachment; filename="${dbName}_dictionary_${getDatetimeStr()}.md"`
|
|
2124
|
+
);
|
|
1317
2125
|
c.header("Content-Type", "text/markdown");
|
|
1318
2126
|
return c.body(md);
|
|
1319
2127
|
} catch (e) {
|
|
@@ -1327,17 +2135,25 @@ function registerApiRoutes(app, dbConfig) {
|
|
|
1327
2135
|
const filename = `${getDbName()}_schema_${getDatetimeStr()}.sql`;
|
|
1328
2136
|
if (type === "sqlite") {
|
|
1329
2137
|
let sqlDump = "-- Drixio SQLite Schema Dump\n\n";
|
|
1330
|
-
const tablesResult = await adapter.query(
|
|
2138
|
+
const tablesResult = await adapter.query(
|
|
2139
|
+
"SELECT sql FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%';"
|
|
2140
|
+
);
|
|
1331
2141
|
for (const row of tablesResult.rows) {
|
|
1332
2142
|
if (row.sql) sqlDump += `${row.sql};
|
|
1333
2143
|
|
|
1334
2144
|
`;
|
|
1335
2145
|
}
|
|
1336
|
-
c.header(
|
|
2146
|
+
c.header(
|
|
2147
|
+
"Content-Disposition",
|
|
2148
|
+
`attachment; filename="${filename}"`
|
|
2149
|
+
);
|
|
1337
2150
|
c.header("Content-Type", "application/sql");
|
|
1338
2151
|
return c.body(sqlDump);
|
|
1339
2152
|
} else {
|
|
1340
|
-
return c.text(
|
|
2153
|
+
return c.text(
|
|
2154
|
+
"Schema-only export for this DB type currently requires manual DDL extraction. Coming soon!",
|
|
2155
|
+
501
|
|
2156
|
+
);
|
|
1341
2157
|
}
|
|
1342
2158
|
} catch (e) {
|
|
1343
2159
|
return c.text(`Schema Export Failed: ${e.message}`, 500);
|
|
@@ -1345,38 +2161,150 @@ function registerApiRoutes(app, dbConfig) {
|
|
|
1345
2161
|
});
|
|
1346
2162
|
app.route("/api", api);
|
|
1347
2163
|
}
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
2164
|
+
function generateSqliteDumpStream(adapter) {
|
|
2165
|
+
return new ReadableStream({
|
|
2166
|
+
async start(controller) {
|
|
2167
|
+
const encoder = new TextEncoder();
|
|
2168
|
+
try {
|
|
2169
|
+
controller.enqueue(
|
|
2170
|
+
encoder.encode("-- Drixio SQLite Fallback Backup\n\n")
|
|
2171
|
+
);
|
|
2172
|
+
const tablesResult = await adapter.query(
|
|
2173
|
+
"SELECT name, sql FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%';"
|
|
2174
|
+
);
|
|
2175
|
+
for (const row of tablesResult.rows) {
|
|
2176
|
+
if (!row.sql) continue;
|
|
2177
|
+
controller.enqueue(encoder.encode(`${row.sql};
|
|
1355
2178
|
|
|
2179
|
+
`));
|
|
2180
|
+
const tableName = row.name;
|
|
2181
|
+
const batchSize = 1e3;
|
|
2182
|
+
let offset = 0;
|
|
2183
|
+
let hasMore = true;
|
|
2184
|
+
while (hasMore) {
|
|
2185
|
+
const batch = await adapter.getData(
|
|
2186
|
+
tableName,
|
|
2187
|
+
batchSize,
|
|
2188
|
+
offset
|
|
2189
|
+
);
|
|
2190
|
+
if (batch.rows.length === 0) {
|
|
2191
|
+
break;
|
|
2192
|
+
}
|
|
2193
|
+
let insertStrs = "";
|
|
2194
|
+
for (const d of batch.rows) {
|
|
2195
|
+
const keys = Object.keys(d).map((k) => adapter.quoteIdentifier(k)).join(", ");
|
|
2196
|
+
const vals = Object.values(d).map((v) => {
|
|
2197
|
+
if (v === null) return "NULL";
|
|
2198
|
+
if (typeof v === "number") return v;
|
|
2199
|
+
return `'${String(v).replace(/'/g, "''")}'`;
|
|
2200
|
+
}).join(", ");
|
|
2201
|
+
insertStrs += `INSERT INTO ${adapter.quoteIdentifier(tableName)} (${keys}) VALUES (${vals});
|
|
1356
2202
|
`;
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
2203
|
+
}
|
|
2204
|
+
controller.enqueue(encoder.encode(insertStrs));
|
|
2205
|
+
offset += batchSize;
|
|
2206
|
+
if (batch.rows.length < batchSize) {
|
|
2207
|
+
hasMore = false;
|
|
2208
|
+
}
|
|
2209
|
+
}
|
|
2210
|
+
controller.enqueue(encoder.encode("\n"));
|
|
2211
|
+
}
|
|
2212
|
+
controller.close();
|
|
2213
|
+
} catch (e) {
|
|
2214
|
+
controller.enqueue(
|
|
2215
|
+
encoder.encode(`-- Error generating backup: ${e}
|
|
2216
|
+
`)
|
|
2217
|
+
);
|
|
2218
|
+
controller.close();
|
|
1367
2219
|
}
|
|
1368
|
-
sqlDump += "\n";
|
|
1369
2220
|
}
|
|
1370
|
-
}
|
|
1371
|
-
|
|
2221
|
+
});
|
|
2222
|
+
}
|
|
2223
|
+
function generateMysqlDumpStream(adapter) {
|
|
2224
|
+
return new ReadableStream({
|
|
2225
|
+
async start(controller) {
|
|
2226
|
+
const encoder = new TextEncoder();
|
|
2227
|
+
try {
|
|
2228
|
+
controller.enqueue(
|
|
2229
|
+
encoder.encode("-- Drixio MySQL Fallback Backup\n\n")
|
|
2230
|
+
);
|
|
2231
|
+
const tables = await adapter.getTables();
|
|
2232
|
+
for (const table of tables) {
|
|
2233
|
+
try {
|
|
2234
|
+
const createTableResult = await adapter.query(
|
|
2235
|
+
`SHOW CREATE TABLE ${adapter.quoteIdentifier(table)}`
|
|
2236
|
+
);
|
|
2237
|
+
if (createTableResult.rows && createTableResult.rows.length > 0) {
|
|
2238
|
+
const row = createTableResult.rows[0];
|
|
2239
|
+
const vals = Object.values(row);
|
|
2240
|
+
const createSql = row["Create Table"] || row["Create View"] || (vals.length > 1 ? vals[1] : null);
|
|
2241
|
+
if (createSql) {
|
|
2242
|
+
controller.enqueue(encoder.encode(`${createSql};
|
|
2243
|
+
|
|
2244
|
+
`));
|
|
2245
|
+
}
|
|
2246
|
+
}
|
|
2247
|
+
const batchSize = 1e3;
|
|
2248
|
+
let offset = 0;
|
|
2249
|
+
let hasMore = true;
|
|
2250
|
+
while (hasMore) {
|
|
2251
|
+
const batch = await adapter.getData(
|
|
2252
|
+
table,
|
|
2253
|
+
batchSize,
|
|
2254
|
+
offset
|
|
2255
|
+
);
|
|
2256
|
+
if (batch.rows.length === 0) {
|
|
2257
|
+
break;
|
|
2258
|
+
}
|
|
2259
|
+
let insertStrs = "";
|
|
2260
|
+
for (const d of batch.rows) {
|
|
2261
|
+
const keys = Object.keys(d).map((k) => adapter.quoteIdentifier(k)).join(", ");
|
|
2262
|
+
const vals = Object.values(d).map((v) => {
|
|
2263
|
+
if (v === null) return "NULL";
|
|
2264
|
+
if (typeof v === "number") return v;
|
|
2265
|
+
let str = String(v);
|
|
2266
|
+
str = str.replace(/\\/g, "\\\\");
|
|
2267
|
+
str = str.replace(/'/g, "''");
|
|
2268
|
+
str = str.replace(/\n/g, "\\n");
|
|
2269
|
+
str = str.replace(/\r/g, "\\r");
|
|
2270
|
+
return `'${str}'`;
|
|
2271
|
+
}).join(", ");
|
|
2272
|
+
insertStrs += `INSERT INTO ${adapter.quoteIdentifier(table)} (${keys}) VALUES (${vals});
|
|
1372
2273
|
`;
|
|
1373
|
-
|
|
1374
|
-
|
|
2274
|
+
}
|
|
2275
|
+
controller.enqueue(encoder.encode(insertStrs));
|
|
2276
|
+
offset += batchSize;
|
|
2277
|
+
if (batch.rows.length < batchSize) {
|
|
2278
|
+
hasMore = false;
|
|
2279
|
+
}
|
|
2280
|
+
}
|
|
2281
|
+
controller.enqueue(encoder.encode("\n"));
|
|
2282
|
+
} catch (tableErr) {
|
|
2283
|
+
controller.enqueue(
|
|
2284
|
+
encoder.encode(
|
|
2285
|
+
`-- Error backing up table ${table}: ${tableErr}
|
|
2286
|
+
|
|
2287
|
+
`
|
|
2288
|
+
)
|
|
2289
|
+
);
|
|
2290
|
+
}
|
|
2291
|
+
}
|
|
2292
|
+
controller.close();
|
|
2293
|
+
} catch (e) {
|
|
2294
|
+
controller.enqueue(
|
|
2295
|
+
encoder.encode(`-- Error generating backup: ${e}
|
|
2296
|
+
`)
|
|
2297
|
+
);
|
|
2298
|
+
controller.close();
|
|
2299
|
+
}
|
|
2300
|
+
}
|
|
2301
|
+
});
|
|
1375
2302
|
}
|
|
1376
2303
|
var init_api = __esm({
|
|
1377
2304
|
"src/server/api.ts"() {
|
|
1378
2305
|
"use strict";
|
|
1379
2306
|
init_factory();
|
|
2307
|
+
init_seeder();
|
|
1380
2308
|
}
|
|
1381
2309
|
});
|
|
1382
2310
|
|
|
@@ -1658,40 +2586,6 @@ __export(seed_exports, {
|
|
|
1658
2586
|
runSeedCommand: () => runSeedCommand
|
|
1659
2587
|
});
|
|
1660
2588
|
import pc14 from "picocolors";
|
|
1661
|
-
function generateRandomString(length) {
|
|
1662
|
-
const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
|
1663
|
-
let result = "";
|
|
1664
|
-
for (let i = 0; i < length; i++) result += chars.charAt(Math.floor(Math.random() * chars.length));
|
|
1665
|
-
return result;
|
|
1666
|
-
}
|
|
1667
|
-
function generateRandomEmail() {
|
|
1668
|
-
const domains = ["gmail.com", "yahoo.com", "outlook.com", "example.com"];
|
|
1669
|
-
const name = generateRandomString(8).toLowerCase();
|
|
1670
|
-
const domain = domains[Math.floor(Math.random() * domains.length)];
|
|
1671
|
-
return `${name}@${domain}`;
|
|
1672
|
-
}
|
|
1673
|
-
function generateFakeData(col) {
|
|
1674
|
-
const name = col.name.toLowerCase();
|
|
1675
|
-
if (name.includes("email")) return generateRandomEmail();
|
|
1676
|
-
if (name.includes("phone")) return `+1${Math.floor(Math.random() * 9e9 + 1e9)}`;
|
|
1677
|
-
if (name.includes("name")) return `User_${generateRandomString(5)}`;
|
|
1678
|
-
if (name.includes("url") || name.includes("link")) return `https://example.com/${generateRandomString(6)}`;
|
|
1679
|
-
if (name.includes("date") || name.includes("time") || name.includes("created") || name.includes("updated")) {
|
|
1680
|
-
const d = new Date(Date.now() - Math.floor(Math.random() * 1e10));
|
|
1681
|
-
return d.toISOString().replace("T", " ").slice(0, 19);
|
|
1682
|
-
}
|
|
1683
|
-
const type = col.type.toLowerCase();
|
|
1684
|
-
if (type.includes("int") || type.includes("num") || type.includes("float") || type.includes("double")) {
|
|
1685
|
-
return Math.floor(Math.random() * 1e3);
|
|
1686
|
-
}
|
|
1687
|
-
if (type.includes("bool") || type === "tinyint(1)") {
|
|
1688
|
-
return Math.random() > 0.5;
|
|
1689
|
-
}
|
|
1690
|
-
if (type.includes("char") || type.includes("text") || type.includes("string")) {
|
|
1691
|
-
return generateRandomString(10);
|
|
1692
|
-
}
|
|
1693
|
-
return generateRandomString(5);
|
|
1694
|
-
}
|
|
1695
2589
|
async function runSeedCommand(dbConfig, args) {
|
|
1696
2590
|
if (dbConfig.type === "unknown") {
|
|
1697
2591
|
console.log(pc14.red("Error: No database connection found. Cannot run seed."));
|
|
@@ -1708,7 +2602,7 @@ async function runSeedCommand(dbConfig, args) {
|
|
|
1708
2602
|
process.exit(1);
|
|
1709
2603
|
}
|
|
1710
2604
|
tableName = await select11({
|
|
1711
|
-
message: "Which table do you want to seed with fake data?",
|
|
2605
|
+
message: "Which table do you want to seed with realistic fake data?",
|
|
1712
2606
|
choices: allTables.map((t) => ({ name: t, value: t }))
|
|
1713
2607
|
});
|
|
1714
2608
|
}
|
|
@@ -1722,52 +2616,23 @@ async function runSeedCommand(dbConfig, args) {
|
|
|
1722
2616
|
}
|
|
1723
2617
|
}
|
|
1724
2618
|
console.log(pc14.cyan(`
|
|
1725
|
-
Analyzing schema for table '${tableName}'...`));
|
|
1726
|
-
let schema;
|
|
2619
|
+
Analyzing schema & foreign keys for table '${tableName}'...`));
|
|
1727
2620
|
try {
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
}
|
|
1737
|
-
console.log(pc14.cyan(`Generating ${count} records...`));
|
|
1738
|
-
const rowsToInsert = [];
|
|
1739
|
-
for (let i = 0; i < count; i++) {
|
|
1740
|
-
const row = {};
|
|
1741
|
-
for (const col of targetCols.length > 0 ? targetCols : schema) {
|
|
1742
|
-
row[col.name] = generateFakeData(col);
|
|
1743
|
-
}
|
|
1744
|
-
rowsToInsert.push(row);
|
|
1745
|
-
}
|
|
1746
|
-
const CHUNK_SIZE = 500;
|
|
1747
|
-
let inserted = 0;
|
|
1748
|
-
try {
|
|
1749
|
-
if (dbConfig.type === "sqlite") await adapter.executeSql("PRAGMA foreign_keys = OFF;");
|
|
1750
|
-
else if (dbConfig.type === "mysql") await adapter.executeSql("SET FOREIGN_KEY_CHECKS = 0;");
|
|
1751
|
-
else if (dbConfig.type === "postgres") await adapter.executeSql("SET session_replication_role = replica;");
|
|
1752
|
-
for (let i = 0; i < rowsToInsert.length; i += CHUNK_SIZE) {
|
|
1753
|
-
const chunk = rowsToInsert.slice(i, i + CHUNK_SIZE);
|
|
1754
|
-
await adapter.insert(tableName, chunk);
|
|
1755
|
-
inserted += chunk.length;
|
|
1756
|
-
process.stdout.write(`\r${pc14.dim(`Progress: ${inserted} / ${count}`)}`);
|
|
1757
|
-
}
|
|
2621
|
+
const inserted = await generateAndInsertMockData(
|
|
2622
|
+
adapter,
|
|
2623
|
+
tableName,
|
|
2624
|
+
count,
|
|
2625
|
+
(current, total) => {
|
|
2626
|
+
process.stdout.write(`\r${pc14.dim(`Progress: ${current} / ${total}`)}`);
|
|
2627
|
+
}
|
|
2628
|
+
);
|
|
1758
2629
|
console.log(pc14.green(`
|
|
1759
2630
|
|
|
1760
|
-
\u2714 Successfully seeded ${inserted}
|
|
2631
|
+
\u2714 Successfully generated and seeded ${inserted} realistic records into ${tableName}!`));
|
|
1761
2632
|
} catch (e) {
|
|
1762
2633
|
console.log(pc14.red(`
|
|
1763
2634
|
\u2718 Seed failed: ${e.message}`));
|
|
1764
2635
|
} finally {
|
|
1765
|
-
try {
|
|
1766
|
-
if (dbConfig.type === "sqlite") await adapter.executeSql("PRAGMA foreign_keys = ON;");
|
|
1767
|
-
else if (dbConfig.type === "mysql") await adapter.executeSql("SET FOREIGN_KEY_CHECKS = 1;");
|
|
1768
|
-
else if (dbConfig.type === "postgres") await adapter.executeSql("SET session_replication_role = DEFAULT;");
|
|
1769
|
-
} catch (e) {
|
|
1770
|
-
}
|
|
1771
2636
|
await adapter.close();
|
|
1772
2637
|
}
|
|
1773
2638
|
process.exit(0);
|
|
@@ -1776,6 +2641,7 @@ var init_seed = __esm({
|
|
|
1776
2641
|
"subcommands/seed.ts"() {
|
|
1777
2642
|
"use strict";
|
|
1778
2643
|
init_factory();
|
|
2644
|
+
init_seeder();
|
|
1779
2645
|
}
|
|
1780
2646
|
});
|
|
1781
2647
|
|