drixio 1.1.9 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -28,27 +28,201 @@ var init_types = __esm({
28
28
  // src/logic/loader.ts
29
29
  import fs from "fs/promises";
30
30
  import path from "path";
31
+ function parseEnvContent(content) {
32
+ const env = {};
33
+ const lines = content.split(/\r?\n/);
34
+ for (let line of lines) {
35
+ line = line.trim();
36
+ if (!line || line.startsWith("#")) continue;
37
+ if (line.startsWith("export ")) {
38
+ line = line.slice(7).trim();
39
+ }
40
+ const eqIdx = line.indexOf("=");
41
+ if (eqIdx === -1) continue;
42
+ const key = line.slice(0, eqIdx).trim();
43
+ let val = line.slice(eqIdx + 1).trim();
44
+ if (val.startsWith('"')) {
45
+ const endQuote = val.indexOf('"', 1);
46
+ val = endQuote !== -1 ? val.slice(1, endQuote) : val.slice(1);
47
+ } else if (val.startsWith("'")) {
48
+ const endQuote = val.indexOf("'", 1);
49
+ val = endQuote !== -1 ? val.slice(1, endQuote) : val.slice(1);
50
+ } else {
51
+ const commentIdx = val.indexOf("#");
52
+ if (commentIdx !== -1) {
53
+ val = val.slice(0, commentIdx).trim();
54
+ }
55
+ }
56
+ if (key) {
57
+ env[key] = val;
58
+ }
59
+ }
60
+ return env;
61
+ }
62
+ async function loadCascadedEnv(cwd) {
63
+ const envFiles = [
64
+ ".env",
65
+ ".env.production",
66
+ ".env.development",
67
+ ".env.local",
68
+ ".env.development.local"
69
+ ];
70
+ const mergedEnv = {};
71
+ for (const file of envFiles) {
72
+ try {
73
+ const filePath = path.join(cwd, file);
74
+ const content = await fs.readFile(filePath, "utf-8");
75
+ const parsed = parseEnvContent(content);
76
+ Object.assign(mergedEnv, parsed);
77
+ } catch {
78
+ }
79
+ }
80
+ for (const [key, val] of Object.entries(process.env)) {
81
+ if (typeof val === "string" && val.trim() !== "") {
82
+ mergedEnv[key] = val.trim();
83
+ }
84
+ }
85
+ return mergedEnv;
86
+ }
87
+ function classifyDatabaseUrl(rawUrl, cwd) {
88
+ let trimmed = rawUrl.trim();
89
+ if (!trimmed) return null;
90
+ if (trimmed.startsWith('"') && trimmed.endsWith('"') || trimmed.startsWith("'") && trimmed.endsWith("'")) {
91
+ trimmed = trimmed.slice(1, -1).trim();
92
+ }
93
+ if (trimmed.startsWith("postgres://") || trimmed.startsWith("postgresql://")) {
94
+ return { type: "postgres", targetUrl: trimmed };
95
+ }
96
+ if (trimmed.startsWith("mysql://")) {
97
+ return { type: "mysql", targetUrl: trimmed };
98
+ }
99
+ const lower = trimmed.toLowerCase();
100
+ const isSqlitePrefix = lower.startsWith("file:") || lower.startsWith("sqlite://") || lower.startsWith("sqlite:");
101
+ const isSqliteExt = lower.endsWith(".db") || lower.endsWith(".sqlite") || lower.endsWith(".sqlite3") || lower.endsWith(".db3");
102
+ const isMemory = lower === ":memory:";
103
+ if (isSqlitePrefix || isSqliteExt || isMemory) {
104
+ let cleanPath = trimmed.replace(/^sqlite:\/\//i, "").replace(/^sqlite:/i, "").replace(/^file:/i, "");
105
+ if (cleanPath === ":memory:") {
106
+ return { type: "sqlite", targetUrl: ":memory:" };
107
+ }
108
+ if (!path.isAbsolute(cleanPath)) {
109
+ cleanPath = path.resolve(cwd, cleanPath);
110
+ }
111
+ return { type: "sqlite", targetUrl: cleanPath };
112
+ }
113
+ return null;
114
+ }
115
+ function buildUrlFromComponentEnv(env) {
116
+ const host = env.DB_HOST || env.DATABASE_HOST || env.MYSQL_HOST || env.POSTGRES_HOST || env.PGHOST;
117
+ const dbName = env.DB_NAME || env.DB_DATABASE || env.DATABASE_NAME || env.MYSQL_DATABASE || env.POSTGRES_DB || env.PGDATABASE;
118
+ if (!host || !dbName) return null;
119
+ const driver = (env.DB_CONNECTION || env.DB_TYPE || env.DB_DRIVER || "").toLowerCase();
120
+ const portStr = env.DB_PORT || env.MYSQL_PORT || env.POSTGRES_PORT || env.PGPORT;
121
+ let type = "mysql";
122
+ if (driver.includes("postgres") || driver.includes("pgsql") || driver.includes("pg") || env.POSTGRES_HOST || env.PGHOST || portStr === "5432") {
123
+ type = "postgres";
124
+ } else if (driver.includes("mysql") || driver.includes("mariadb") || env.MYSQL_HOST || portStr === "3306") {
125
+ type = "mysql";
126
+ }
127
+ const defaultPort = type === "postgres" ? 5432 : 3306;
128
+ const port = portStr ? parseInt(portStr, 10) || defaultPort : defaultPort;
129
+ const user = env.DB_USER || env.DB_USERNAME || env.MYSQL_USER || env.POSTGRES_USER || env.PGUSER || (type === "postgres" ? "postgres" : "root");
130
+ const pass = env.DB_PASSWORD || env.DB_PASS || env.MYSQL_PASSWORD || env.POSTGRES_PASSWORD || env.PGPASSWORD || "";
131
+ const auth = pass ? `${encodeURIComponent(user)}:${encodeURIComponent(pass)}` : encodeURIComponent(user);
132
+ const targetUrl = `${type}://${auth}@${host}:${port}/${dbName}`;
133
+ return { type, targetUrl };
134
+ }
31
135
  async function detectDatabase(databaseUrl) {
32
136
  const cwd = process.cwd();
33
137
  if (databaseUrl) {
34
- let type = "sqlite";
35
- if (databaseUrl.startsWith("postgres://") || databaseUrl.startsWith("postgresql://")) {
36
- type = "postgres";
37
- } else if (databaseUrl.startsWith("mysql://")) {
38
- type = "mysql";
39
- } else {
40
- type = "sqlite";
41
- }
42
- let targetUrl = databaseUrl.replace("file:", "");
43
- if (type === "sqlite" && !path.isAbsolute(targetUrl)) {
44
- targetUrl = path.resolve(cwd, targetUrl);
138
+ const classified = classifyDatabaseUrl(databaseUrl, cwd);
139
+ if (classified) {
140
+ return {
141
+ type: classified.type,
142
+ targetUrl: classified.targetUrl,
143
+ source: "manual"
144
+ };
45
145
  }
46
146
  return {
47
- type,
48
- targetUrl,
147
+ type: "unknown",
148
+ targetUrl: databaseUrl,
49
149
  source: "manual"
50
150
  };
51
151
  }
152
+ const allEnv = await loadCascadedEnv(cwd);
153
+ const prismaPaths = [
154
+ path.join(cwd, "prisma", "schema.prisma"),
155
+ path.join(cwd, "src", "prisma", "schema.prisma")
156
+ ];
157
+ for (const schemaPath of prismaPaths) {
158
+ try {
159
+ const schemaContent = await fs.readFile(schemaPath, "utf-8");
160
+ const providerMatch = schemaContent.match(
161
+ /provider\s*=\s*["']([^"']+)["']/
162
+ );
163
+ const urlMatch = schemaContent.match(
164
+ /url\s*=\s*(?:env\(["']([^"']+)["']\)|["']([^"']+)["'])/
165
+ );
166
+ if (providerMatch) {
167
+ let provider = providerMatch[1];
168
+ if (provider === "postgresql") provider = "postgres";
169
+ let rawUrl;
170
+ if (urlMatch) {
171
+ if (urlMatch[1]) {
172
+ rawUrl = allEnv[urlMatch[1]];
173
+ } else if (urlMatch[2]) {
174
+ rawUrl = urlMatch[2];
175
+ }
176
+ }
177
+ if (rawUrl) {
178
+ if (provider === "sqlite") {
179
+ let cleanPath = rawUrl.replace(/^file:/i, "");
180
+ if (!path.isAbsolute(cleanPath)) {
181
+ cleanPath = path.resolve(
182
+ path.dirname(schemaPath),
183
+ cleanPath
184
+ );
185
+ }
186
+ return {
187
+ type: "sqlite",
188
+ targetUrl: cleanPath,
189
+ source: ".env"
190
+ };
191
+ }
192
+ const classified = classifyDatabaseUrl(rawUrl, cwd);
193
+ if (classified) {
194
+ return {
195
+ type: classified.type,
196
+ targetUrl: classified.targetUrl,
197
+ source: ".env"
198
+ };
199
+ }
200
+ }
201
+ }
202
+ } catch {
203
+ }
204
+ }
205
+ for (const key of DB_ENV_KEYS) {
206
+ const candidate = allEnv[key];
207
+ if (candidate) {
208
+ const classified = classifyDatabaseUrl(candidate, cwd);
209
+ if (classified) {
210
+ return {
211
+ type: classified.type,
212
+ targetUrl: classified.targetUrl,
213
+ source: ".env"
214
+ };
215
+ }
216
+ }
217
+ }
218
+ const assembled = buildUrlFromComponentEnv(allEnv);
219
+ if (assembled) {
220
+ return {
221
+ type: assembled.type,
222
+ targetUrl: assembled.targetUrl,
223
+ source: ".env"
224
+ };
225
+ }
52
226
  const searchDirs = [
53
227
  ".",
54
228
  "prisma",
@@ -61,9 +235,10 @@ async function detectDatabase(databaseUrl) {
61
235
  try {
62
236
  const targetDir = path.join(cwd, dir);
63
237
  const files = await fs.readdir(targetDir);
64
- const sqliteFile = files.find(
65
- (file) => file.endsWith(".db") || file.endsWith(".sqlite") || file.endsWith(".sqlite3")
66
- );
238
+ const sqliteFile = files.find((file) => {
239
+ const lower = file.toLowerCase();
240
+ return lower.endsWith(".db") || lower.endsWith(".sqlite") || lower.endsWith(".sqlite3") || lower.endsWith(".db3");
241
+ });
67
242
  if (sqliteFile) {
68
243
  return {
69
244
  type: "sqlite",
@@ -74,81 +249,6 @@ async function detectDatabase(databaseUrl) {
74
249
  } catch {
75
250
  }
76
251
  }
77
- const prismaSchemaPath = path.join(cwd, "prisma", "schema.prisma");
78
- try {
79
- const schemaContent = await fs.readFile(prismaSchemaPath, "utf-8");
80
- const providerMatch = schemaContent.match(
81
- /provider\s*=\s*["']([^"']+)["']/
82
- );
83
- const urlMatch = schemaContent.match(
84
- /url\s*=\s*(?:env\(["']([^"']+)["']\)|["']([^"']+)["'])/
85
- );
86
- if (providerMatch) {
87
- let provider = providerMatch[1];
88
- if (provider === "postgresql") provider = "postgres";
89
- let targetUrl = "";
90
- if (urlMatch && urlMatch[2]) {
91
- targetUrl = urlMatch[2];
92
- if (targetUrl.startsWith("file:")) {
93
- targetUrl = path.join(
94
- cwd,
95
- "prisma",
96
- targetUrl.replace("file:", "")
97
- );
98
- }
99
- }
100
- if (targetUrl && (provider === "sqlite" || provider === "postgres" || provider === "mysql")) {
101
- return {
102
- type: provider,
103
- targetUrl,
104
- source: ".env"
105
- };
106
- }
107
- }
108
- } catch {
109
- }
110
- const envPath = path.join(cwd, ".env");
111
- try {
112
- const envContent = await fs.readFile(envPath, "utf-8");
113
- const dbKeys = [
114
- "DATABASE_URL",
115
- "DB_URL",
116
- "POSTGRES_URL",
117
- "POSTGRES_PRISMA_URL",
118
- "MYSQL_URL"
119
- ];
120
- for (const key of dbKeys) {
121
- const regex = new RegExp(`${key}\\s*=\\s*["']?([^"'\\r\\n]+)["']?`);
122
- const match = envContent.match(regex);
123
- if (match) {
124
- const url = match[1];
125
- if (url.startsWith("file:") || url.endsWith(".db") || url.includes(".sqlite")) {
126
- let targetUrl = url.replace("file:", "");
127
- if (!path.isAbsolute(targetUrl)) {
128
- targetUrl = path.resolve(cwd, targetUrl);
129
- }
130
- return {
131
- type: "sqlite",
132
- targetUrl,
133
- source: ".env"
134
- };
135
- } else if (url.startsWith("postgres://") || url.startsWith("postgresql://")) {
136
- return {
137
- type: "postgres",
138
- targetUrl: url,
139
- source: ".env"
140
- };
141
- } else if (url.startsWith("mysql://")) {
142
- return {
143
- type: "mysql",
144
- targetUrl: url,
145
- source: ".env"
146
- };
147
- }
148
- }
149
- }
150
- } catch (error) {
151
- }
152
252
  return {
153
253
  type: "unknown",
154
254
  targetUrl: "",
@@ -161,7 +261,7 @@ async function saveDatabaseUrl(url) {
161
261
  let envContent = "";
162
262
  try {
163
263
  envContent = await fs.readFile(envPath, "utf-8");
164
- } catch (e) {
264
+ } catch {
165
265
  }
166
266
  const regex = /DATABASE_URL\s*=\s*["']?([^"'\r\n]+)["']?/;
167
267
  if (regex.test(envContent)) {
@@ -183,10 +283,351 @@ async function saveDatabaseUrl(url) {
183
283
  );
184
284
  }
185
285
  }
286
+ function assembleConnectionUrl(type, host, port, user, password, database) {
287
+ const targetHost = host || "localhost";
288
+ const targetPort = port || (type === "postgres" ? "5432" : "3306");
289
+ const targetUser = user || (type === "postgres" ? "postgres" : "root");
290
+ const auth = password ? `${encodeURIComponent(targetUser)}:${encodeURIComponent(password)}` : encodeURIComponent(targetUser);
291
+ const defaultDb = type === "postgres" ? "postgres" : "";
292
+ const dbNameStr = database || defaultDb;
293
+ return `${type}://${auth}@${targetHost}:${targetPort}/${dbNameStr}`;
294
+ }
295
+ function resolveLocalDbPath(targetUrl, cwd) {
296
+ const cleanPath = targetUrl.replace(/^file:/, "").trim();
297
+ return path.isAbsolute(cleanPath) ? cleanPath : path.resolve(cwd, cleanPath);
298
+ }
299
+ function isConnectionString(url) {
300
+ const trimmed = url.trim();
301
+ return trimmed.startsWith("postgres://") || trimmed.startsWith("postgresql://") || trimmed.startsWith("mysql://");
302
+ }
303
+ var DB_ENV_KEYS;
186
304
  var init_loader = __esm({
187
305
  "src/logic/loader.ts"() {
188
306
  "use strict";
189
307
  init_types();
308
+ DB_ENV_KEYS = [
309
+ "DATABASE_URL",
310
+ "DB_URL",
311
+ "DATABASE_URI",
312
+ "DB_URI",
313
+ "DIRECT_URL",
314
+ "POSTGRES_URL",
315
+ "POSTGRESQL_URL",
316
+ "POSTGRES_PRISMA_URL",
317
+ "POSTGRES_URL_NON_POOLING",
318
+ "SUPABASE_DB_URL",
319
+ "MYSQL_URL",
320
+ "MYSQL_DATABASE_URL",
321
+ "JAWSDB_URL",
322
+ "CLEARDB_DATABASE_URL"
323
+ ];
324
+ }
325
+ });
326
+
327
+ // src/logic/dialect.ts
328
+ function getDialect(type) {
329
+ switch (type) {
330
+ case "sqlite":
331
+ return new SqliteDialect();
332
+ case "postgres":
333
+ return new PostgresDialect();
334
+ case "mysql":
335
+ return new MysqlDialect();
336
+ default:
337
+ return new SqliteDialect();
338
+ }
339
+ }
340
+ function buildSearchWhereClause(adapter, dbType, columns, searchInput) {
341
+ const searchVal = (searchInput || "").trim();
342
+ if (!searchVal) return "";
343
+ const isSqlCondition = /[=<>]|LIKE|IN|AND|OR/i.test(searchVal);
344
+ if (isSqlCondition) {
345
+ return searchVal;
346
+ }
347
+ const dialect = getDialect(dbType);
348
+ const strCols = columns.filter((c) => {
349
+ const t = c.type.toLowerCase();
350
+ return t.includes("char") || t.includes("text") || t.includes("string") || t.includes("uuid");
351
+ });
352
+ if (strCols.length > 0) {
353
+ const likeOp = dbType === "postgres" ? "ILIKE" : "LIKE";
354
+ const escaped = dialect.escapeString(searchVal);
355
+ const conditions = strCols.map(
356
+ (c) => `${adapter.quoteIdentifier(c.name)} ${likeOp} '%${escaped}%'`
357
+ );
358
+ return conditions.join(" OR ");
359
+ }
360
+ if (columns.length > 0) {
361
+ const col = columns[0];
362
+ const escaped = dialect.escapeString(searchVal);
363
+ return `${adapter.quoteIdentifier(col.name)} = '${escaped}'`;
364
+ }
365
+ return "";
366
+ }
367
+ function formatSqlDefaultValue(rawDefault) {
368
+ if (rawDefault === void 0 || rawDefault === null) return null;
369
+ let trimmed = String(rawDefault).trim();
370
+ if (!trimmed || trimmed.toLowerCase() === "null" || trimmed === "-") {
371
+ return null;
372
+ }
373
+ while (trimmed.startsWith("''") && trimmed.endsWith("''") && trimmed.length > 4) {
374
+ trimmed = trimmed.slice(1, -1);
375
+ }
376
+ if (/^-?\d+(\.\d+)?$/.test(trimmed)) {
377
+ return trimmed;
378
+ }
379
+ const lower = trimmed.toLowerCase();
380
+ if (lower === "true" || lower === "false") {
381
+ return lower.toUpperCase();
382
+ }
383
+ const upper = trimmed.toUpperCase();
384
+ if (upper === "CURRENT_TIMESTAMP" || upper === "CURRENT_DATE" || upper === "CURRENT_TIME" || upper === "NOW()" || upper === "TIMESTAMP" || upper.startsWith("CURRENT_TIMESTAMP") || upper.startsWith("NOW()") || upper.startsWith("GEN_RANDOM_UUID(") || upper.startsWith("UUID_GENERATE_") || upper.startsWith("UUID(") || trimmed.startsWith("(") && trimmed.endsWith(")")) {
385
+ return upper === "TIMESTAMP" ? "CURRENT_TIMESTAMP" : trimmed;
386
+ }
387
+ const pgCastMatch = trimmed.match(/^('[\s\S]*')(?:::[\w\s()]+)$/);
388
+ if (pgCastMatch) {
389
+ return pgCastMatch[1];
390
+ }
391
+ if (trimmed.startsWith("'") && trimmed.endsWith("'") && trimmed.length >= 2) {
392
+ return trimmed;
393
+ }
394
+ if (trimmed.startsWith('"') && trimmed.endsWith('"') && trimmed.length >= 2) {
395
+ const inner = trimmed.slice(1, -1).replace(/'/g, "''");
396
+ return `'${inner}'`;
397
+ }
398
+ const escaped = trimmed.replace(/'/g, "''");
399
+ return `'${escaped}'`;
400
+ }
401
+ function generateExplainQuery(rawSql, dbType) {
402
+ let cleanSql = rawSql.replace(/--.*$/gm, "").replace(/\/\*[\s\S]*?\*\//g, "").trim().replace(/;+$/, "").trim();
403
+ if (!cleanSql) {
404
+ return { sql: "", error: "SQL query is empty after stripping comments" };
405
+ }
406
+ if (/^(CREATE|DROP|ALTER|TRUNCATE)\s+/i.test(cleanSql)) {
407
+ return {
408
+ sql: "",
409
+ error: "DDL statements (CREATE, DROP, ALTER, TRUNCATE) do not have query execution plans to explain."
410
+ };
411
+ }
412
+ const hasUserExplain = /^EXPLAIN\b/i.test(cleanSql);
413
+ let targetSql = cleanSql;
414
+ if (hasUserExplain) {
415
+ targetSql = cleanSql.replace(/^EXPLAIN\s+(QUERY\s+PLAN\s+)?/i, "").replace(
416
+ /^\((ANALYZE|COSTS|VERBOSE|BUFFERS|FORMAT\s+\w+|,|\s)+\)\s*/i,
417
+ ""
418
+ ).trim();
419
+ }
420
+ let explainSql = "";
421
+ const isDmlWrite = /^(INSERT|UPDATE|DELETE)\s+/i.test(targetSql);
422
+ if (dbType === "sqlite") {
423
+ explainSql = `EXPLAIN QUERY PLAN ${targetSql}`;
424
+ } else if (dbType === "postgres") {
425
+ if (isDmlWrite) {
426
+ explainSql = `EXPLAIN (COSTS, VERBOSE) ${targetSql}`;
427
+ } else {
428
+ explainSql = `EXPLAIN (ANALYZE, COSTS, VERBOSE, BUFFERS) ${targetSql}`;
429
+ }
430
+ } else if (dbType === "mysql") {
431
+ explainSql = `EXPLAIN ${targetSql}`;
432
+ } else {
433
+ explainSql = `EXPLAIN ${targetSql}`;
434
+ }
435
+ return { sql: explainSql };
436
+ }
437
+ var SqliteDialect, PostgresDialect, MysqlDialect;
438
+ var init_dialect = __esm({
439
+ "src/logic/dialect.ts"() {
440
+ "use strict";
441
+ SqliteDialect = class {
442
+ quoteIdentifier(name) {
443
+ return `"${name}"`;
444
+ return `"${name.replace(/"/g, '""')}"`;
445
+ }
446
+ escapeString(val) {
447
+ return val.replace(/'/g, "''");
448
+ }
449
+ buildCreateTable(tableName, columns) {
450
+ const lines = columns.map((col) => {
451
+ const isPk = !!(col.isPk || col.primaryKey);
452
+ const tLower = col.type.toLowerCase();
453
+ let typeStr = col.type;
454
+ if (tLower === "integer" || tLower === "int") typeStr = "INTEGER";
455
+ else if (tLower === "text" || tLower === "string") typeStr = "TEXT";
456
+ else if (tLower === "boolean" || tLower === "bool")
457
+ typeStr = "BOOLEAN";
458
+ else if (tLower === "decimal" || tLower === "numeric" || tLower === "float" || tLower === "double")
459
+ typeStr = "REAL";
460
+ else if (tLower === "datetime" || tLower === "timestamp")
461
+ typeStr = "DATETIME";
462
+ else if (col.enumValues && col.enumValues.length > 0) {
463
+ typeStr = `TEXT CHECK(${this.quoteIdentifier(col.name)} IN (${col.enumValues.map((v) => `'${this.escapeString(v)}'`).join(", ")}))`;
464
+ }
465
+ if (isPk && (tLower.includes("int") || typeStr === "INTEGER")) {
466
+ typeStr = "INTEGER PRIMARY KEY AUTOINCREMENT";
467
+ } else if (isPk) {
468
+ typeStr += " PRIMARY KEY";
469
+ } else {
470
+ if (!col.nullable) typeStr += " NOT NULL";
471
+ if (col.isUnique) typeStr += " UNIQUE";
472
+ }
473
+ if (col.defaultValue && col.defaultValue !== "AutoInc" && !col.defaultValue.startsWith("FK ->")) {
474
+ const formatted = formatSqlDefaultValue(col.defaultValue);
475
+ if (formatted !== null) {
476
+ typeStr += ` DEFAULT ${formatted}`;
477
+ }
478
+ }
479
+ return ` ${this.quoteIdentifier(col.name)} ${typeStr}`;
480
+ });
481
+ const fks = columns.filter(
482
+ (col) => col.fkTarget && col.fkTarget.table && col.fkTarget.column
483
+ ).map((col) => {
484
+ let fkStr = ` FOREIGN KEY (${this.quoteIdentifier(col.name)}) REFERENCES ${this.quoteIdentifier(col.fkTarget.table)}(${this.quoteIdentifier(col.fkTarget.column)})`;
485
+ if (col.fkTarget.onDelete && col.fkTarget.onDelete !== "NO ACTION") {
486
+ fkStr += ` ON DELETE ${col.fkTarget.onDelete}`;
487
+ }
488
+ if (col.fkTarget.onUpdate && col.fkTarget.onUpdate !== "NO ACTION") {
489
+ fkStr += ` ON UPDATE ${col.fkTarget.onUpdate}`;
490
+ }
491
+ return fkStr;
492
+ });
493
+ if (fks.length > 0) {
494
+ lines.push(...fks);
495
+ }
496
+ return `CREATE TABLE ${this.quoteIdentifier(tableName)} (
497
+ ${lines.join(",\n")}
498
+ );`;
499
+ }
500
+ };
501
+ PostgresDialect = class {
502
+ quoteIdentifier(name) {
503
+ return `"${name}"`;
504
+ return `"${name.replace(/"/g, '""')}"`;
505
+ }
506
+ escapeString(val) {
507
+ return val.replace(/'/g, "''");
508
+ }
509
+ buildCreateTable(tableName, columns) {
510
+ const lines = columns.map((col) => {
511
+ const isPk = !!(col.isPk || col.primaryKey);
512
+ const tLower = col.type.toLowerCase();
513
+ let typeStr = col.type;
514
+ if (isPk && (tLower === "integer" || tLower === "int")) {
515
+ typeStr = "SERIAL PRIMARY KEY";
516
+ } else {
517
+ if (tLower === "integer" || tLower === "int") typeStr = "INTEGER";
518
+ else if (tLower === "text" || tLower === "string") typeStr = "TEXT";
519
+ else if (tLower === "boolean" || tLower === "bool")
520
+ typeStr = "BOOLEAN";
521
+ else if (tLower === "decimal" || tLower === "numeric")
522
+ typeStr = "NUMERIC";
523
+ else if (tLower === "datetime" || tLower === "timestamp")
524
+ typeStr = "TIMESTAMP";
525
+ else if (col.enumValues && col.enumValues.length > 0) {
526
+ if (col.type && ![
527
+ "enum",
528
+ "varchar",
529
+ "varchar(255)",
530
+ "text",
531
+ "string"
532
+ ].includes(tLower)) {
533
+ typeStr = this.quoteIdentifier(col.type);
534
+ } else {
535
+ typeStr = `VARCHAR(255) CHECK(${this.quoteIdentifier(col.name)} IN (${col.enumValues.map((v) => `'${this.escapeString(v)}'`).join(", ")}))`;
536
+ }
537
+ }
538
+ if (isPk) typeStr += " PRIMARY KEY";
539
+ if (!col.nullable && !isPk) typeStr += " NOT NULL";
540
+ if (col.isUnique && !isPk) typeStr += " UNIQUE";
541
+ }
542
+ if (col.defaultValue && col.defaultValue !== "AutoInc" && !col.defaultValue.startsWith("FK ->")) {
543
+ const formatted = formatSqlDefaultValue(col.defaultValue);
544
+ if (formatted !== null) {
545
+ typeStr += ` DEFAULT ${formatted}`;
546
+ }
547
+ }
548
+ return ` ${this.quoteIdentifier(col.name)} ${typeStr}`;
549
+ });
550
+ const fks = columns.filter(
551
+ (col) => col.fkTarget && col.fkTarget.table && col.fkTarget.column
552
+ ).map((col) => {
553
+ let fkStr = ` FOREIGN KEY (${this.quoteIdentifier(col.name)}) REFERENCES ${this.quoteIdentifier(col.fkTarget.table)}(${this.quoteIdentifier(col.fkTarget.column)})`;
554
+ if (col.fkTarget.onDelete && col.fkTarget.onDelete !== "NO ACTION") {
555
+ fkStr += ` ON DELETE ${col.fkTarget.onDelete}`;
556
+ }
557
+ if (col.fkTarget.onUpdate && col.fkTarget.onUpdate !== "NO ACTION") {
558
+ fkStr += ` ON UPDATE ${col.fkTarget.onUpdate}`;
559
+ }
560
+ return fkStr;
561
+ });
562
+ if (fks.length > 0) {
563
+ lines.push(...fks);
564
+ }
565
+ return `CREATE TABLE ${this.quoteIdentifier(tableName)} (
566
+ ${lines.join(",\n")}
567
+ );`;
568
+ }
569
+ };
570
+ MysqlDialect = class {
571
+ quoteIdentifier(name) {
572
+ return `\`${name}\``;
573
+ return `\`${name.replace(/`/g, "``")}\``;
574
+ }
575
+ escapeString(val) {
576
+ return val.replace(/'/g, "''");
577
+ return val.replace(/\\/g, "\\\\").replace(/'/g, "''");
578
+ }
579
+ buildCreateTable(tableName, columns) {
580
+ const lines = columns.map((col) => {
581
+ const isPk = !!(col.isPk || col.primaryKey);
582
+ const tLower = col.type.toLowerCase();
583
+ let typeStr = col.type;
584
+ if (tLower === "integer" || tLower === "int") typeStr = "INT";
585
+ else if (tLower === "text" || tLower === "string")
586
+ typeStr = "VARCHAR(255)";
587
+ else if (tLower === "boolean" || tLower === "bool")
588
+ typeStr = "BOOLEAN";
589
+ else if (tLower === "decimal" || tLower === "numeric")
590
+ typeStr = "DOUBLE";
591
+ else if (tLower === "datetime" || tLower === "timestamp")
592
+ typeStr = "DATETIME";
593
+ else if (col.enumValues && col.enumValues.length > 0) {
594
+ typeStr = `ENUM(${col.enumValues.map((v) => `'${this.escapeString(v)}'`).join(", ")})`;
595
+ }
596
+ if (isPk && (tLower.includes("int") || typeStr === "INT")) {
597
+ typeStr += " AUTO_INCREMENT PRIMARY KEY";
598
+ } else if (isPk) {
599
+ typeStr += " PRIMARY KEY";
600
+ }
601
+ if (!col.nullable && !isPk) typeStr += " NOT NULL";
602
+ if (col.isUnique && !isPk) typeStr += " UNIQUE";
603
+ if (col.defaultValue && col.defaultValue !== "AutoInc" && !col.defaultValue.startsWith("FK ->")) {
604
+ const formatted = formatSqlDefaultValue(col.defaultValue);
605
+ if (formatted !== null) {
606
+ typeStr += ` DEFAULT ${formatted}`;
607
+ }
608
+ }
609
+ return ` ${this.quoteIdentifier(col.name)} ${typeStr}`;
610
+ });
611
+ const fks = columns.filter(
612
+ (col) => col.fkTarget && col.fkTarget.table && col.fkTarget.column
613
+ ).map((col) => {
614
+ let fkStr = ` FOREIGN KEY (${this.quoteIdentifier(col.name)}) REFERENCES ${this.quoteIdentifier(col.fkTarget.table)}(${this.quoteIdentifier(col.fkTarget.column)})`;
615
+ if (col.fkTarget.onDelete && col.fkTarget.onDelete !== "NO ACTION") {
616
+ fkStr += ` ON DELETE ${col.fkTarget.onDelete}`;
617
+ }
618
+ if (col.fkTarget.onUpdate && col.fkTarget.onUpdate !== "NO ACTION") {
619
+ fkStr += ` ON UPDATE ${col.fkTarget.onUpdate}`;
620
+ }
621
+ return fkStr;
622
+ });
623
+ if (fks.length > 0) {
624
+ lines.push(...fks);
625
+ }
626
+ return `CREATE TABLE ${this.quoteIdentifier(tableName)} (
627
+ ${lines.join(",\n")}
628
+ );`;
629
+ }
630
+ };
190
631
  }
191
632
  });
192
633
 
@@ -195,6 +636,7 @@ var SqliteAdapter;
195
636
  var init_sqlite = __esm({
196
637
  "src/logic/adapters/sqlite.ts"() {
197
638
  "use strict";
639
+ init_dialect();
198
640
  SqliteAdapter = class {
199
641
  dbPath;
200
642
  db = null;
@@ -203,10 +645,14 @@ var init_sqlite = __esm({
203
645
  }
204
646
  async getDb() {
205
647
  if (!this.db) {
206
- let modFs = "node:fs";
207
- const fs16 = await import(modFs);
208
- if (!fs16.existsSync(this.dbPath)) {
209
- throw new Error(`Failed to found database file at: ${this.dbPath}`);
648
+ if (this.dbPath !== ":memory:") {
649
+ let modFs = "node:fs";
650
+ const fs19 = await import(modFs);
651
+ if (!fs19.existsSync(this.dbPath)) {
652
+ throw new Error(
653
+ `Failed to found database file at: ${this.dbPath}`
654
+ );
655
+ }
210
656
  }
211
657
  let mod = "node:sqlite";
212
658
  const sqlite = await import(mod);
@@ -222,12 +668,17 @@ var init_sqlite = __esm({
222
668
  const db = await this.getDb();
223
669
  const vQuery = db.prepare("SELECT sqlite_version() as v");
224
670
  const vRow = vQuery.get();
225
- let modFs = "node:fs";
226
- const fs16 = await import(modFs);
227
- const stats = fs16.statSync(this.dbPath);
228
- let modPath = "node:path";
229
- const path17 = await import(modPath);
230
- const dbName = path17.basename(this.dbPath);
671
+ let sizeBytes = 0;
672
+ let dbName = ":memory:";
673
+ if (this.dbPath !== ":memory:") {
674
+ let modFs = "node:fs";
675
+ const fs19 = await import(modFs);
676
+ const stats = fs19.statSync(this.dbPath);
677
+ sizeBytes = stats.size;
678
+ let modPath = "node:path";
679
+ const path20 = await import(modPath);
680
+ dbName = path20.basename(this.dbPath);
681
+ }
231
682
  return {
232
683
  status: "connected",
233
684
  dbType: "sqlite",
@@ -235,7 +686,7 @@ var init_sqlite = __esm({
235
686
  version: vRow?.v,
236
687
  activeConnections: 1,
237
688
  // SQLite is single file, essentially 1 active connection for the app
238
- sizeBytes: stats.size,
689
+ sizeBytes,
239
690
  uptime: process.uptime()
240
691
  };
241
692
  } catch (e) {
@@ -463,15 +914,21 @@ var init_sqlite = __esm({
463
914
  const isCompositePk = pkColumns.length > 1;
464
915
  const colDefs = [];
465
916
  for (const col of newColumns) {
466
- let def = `${this.quoteIdentifier(col.name)} ${col.type || "TEXT"}`;
917
+ let colType = col.type || "TEXT";
918
+ if (col.enumValues && col.enumValues.length > 0) {
919
+ const vals = col.enumValues.map((v) => `'${v.replace(/'/g, "''")}'`).join(", ");
920
+ colType = `TEXT CHECK(${this.quoteIdentifier(col.name)} IN (${vals}))`;
921
+ }
922
+ let def = `${this.quoteIdentifier(col.name)} ${colType}`;
467
923
  if (col.isPk && !isCompositePk) {
468
924
  def += " PRIMARY KEY";
469
925
  }
470
926
  if (!col.nullable && (!col.isPk || isCompositePk)) {
471
927
  def += " NOT NULL";
472
928
  }
473
- if (col.defaultValue !== void 0 && col.defaultValue !== null && col.defaultValue !== "") {
474
- def += ` DEFAULT '${String(col.defaultValue).replace(/'/g, "''")}'`;
929
+ const formattedDef = formatSqlDefaultValue(col.defaultValue);
930
+ if (formattedDef !== null) {
931
+ def += ` DEFAULT ${formattedDef}`;
475
932
  }
476
933
  if (col.fkTarget && col.fkTarget.table && col.fkTarget.column) {
477
934
  def += ` REFERENCES ${this.quoteIdentifier(col.fkTarget.table)}(${this.quoteIdentifier(col.fkTarget.column)})`;
@@ -549,23 +1006,74 @@ var init_sqlite = __esm({
549
1006
 
550
1007
  // src/logic/adapters/postgres.ts
551
1008
  import pg from "pg";
1009
+ function buildPgPoolConfig(connectionString) {
1010
+ const config = {
1011
+ connectionString,
1012
+ connectionTimeoutMillis: 1e4
1013
+ // 10s timeout to prevent hanging on unreachable hosts
1014
+ };
1015
+ try {
1016
+ const lower = connectionString.toLowerCase();
1017
+ const isExplicitSslDisable = lower.includes("sslmode=disable");
1018
+ const isExplicitSslRequire = lower.includes("sslmode=require") || lower.includes("sslmode=prefer") || lower.includes("ssl=true") || lower.includes("ssl=1");
1019
+ let isCloudProvider = false;
1020
+ const match = connectionString.match(/@([^/:?#]+)/);
1021
+ if (match && match[1]) {
1022
+ const host = match[1].toLowerCase();
1023
+ isCloudProvider = host.includes("supabase.") || host.includes("neon.tech") || host.includes("railway.") || host.includes("render.com") || host.includes("cockroach") || host.includes("aiven") || host.includes("rds.amazonaws.com");
1024
+ }
1025
+ if (!isExplicitSslDisable && (isExplicitSslRequire || isCloudProvider)) {
1026
+ config.ssl = {
1027
+ rejectUnauthorized: false
1028
+ };
1029
+ }
1030
+ } catch {
1031
+ }
1032
+ return config;
1033
+ }
552
1034
  var PostgresAdapter;
553
1035
  var init_postgres = __esm({
554
1036
  "src/logic/adapters/postgres.ts"() {
555
1037
  "use strict";
556
- PostgresAdapter = class {
1038
+ PostgresAdapter = class _PostgresAdapter {
557
1039
  connectionString;
558
1040
  pool = null;
1041
+ currentSchema;
559
1042
  constructor(connection) {
560
1043
  this.connectionString = connection;
1044
+ this.currentSchema = _PostgresAdapter.extractSchema(connection);
1045
+ }
1046
+ /**
1047
+ * Extract the schema parameter from a PostgreSQL connection URL.
1048
+ * Supports both `?schema=xxx` (Prisma-style) and `?options=-c search_path%3Dxxx`.
1049
+ * Defaults to 'public' if not found.
1050
+ */
1051
+ static extractSchema(url) {
1052
+ try {
1053
+ const qIdx = url.indexOf("?");
1054
+ if (qIdx === -1) return "public";
1055
+ const params = new URLSearchParams(url.slice(qIdx + 1));
1056
+ const schema = params.get("schema");
1057
+ if (schema) return schema;
1058
+ const opts = params.get("options");
1059
+ if (opts) {
1060
+ const m = opts.match(/search_path[=](\w+)/);
1061
+ if (m) return m[1];
1062
+ }
1063
+ } catch {
1064
+ }
1065
+ return "public";
561
1066
  }
562
1067
  getPool() {
563
1068
  if (!this.pool) {
564
- this.pool = new pg.Pool({
565
- connectionString: this.connectionString
566
- });
1069
+ this.pool = new pg.Pool(buildPgPoolConfig(this.connectionString));
567
1070
  this.pool.on("error", () => {
568
1071
  });
1072
+ this.pool.on("connect", (client) => {
1073
+ const quoted = this.currentSchema.replace(/"/g, '""');
1074
+ client.query(`SET search_path TO "${quoted}", public`).catch(() => {
1075
+ });
1076
+ });
569
1077
  }
570
1078
  return this.pool;
571
1079
  }
@@ -626,15 +1134,15 @@ var init_postgres = __esm({
626
1134
  const query = `
627
1135
  SELECT tablename
628
1136
  FROM pg_catalog.pg_tables
629
- WHERE schemaname = 'public'
1137
+ WHERE schemaname = $1
630
1138
  ORDER BY tablename;
631
1139
  `;
632
- const res = await this.getPool().query(query);
1140
+ const res = await this.getPool().query(query, [this.currentSchema]);
633
1141
  return res.rows.map((row) => row.tablename);
634
1142
  }
635
1143
  async getSchema(tableName) {
636
1144
  const query = `
637
- SELECT c.column_name, c.data_type, c.is_nullable, c.column_default,
1145
+ SELECT c.column_name, c.data_type, c.udt_name, c.is_nullable, c.column_default,
638
1146
  (SELECT count(*)
639
1147
  FROM information_schema.key_column_usage kcu
640
1148
  JOIN information_schema.table_constraints tc
@@ -692,11 +1200,44 @@ var init_postgres = __esm({
692
1200
  AND kcu.column_name = c.column_name
693
1201
  LIMIT 1) as fk_on_update
694
1202
  FROM information_schema.columns c
695
- WHERE c.table_name = $1 AND c.table_schema = 'public'
1203
+ WHERE c.table_name = $1 AND c.table_schema = $2
696
1204
  ORDER BY c.ordinal_position;
697
1205
  `;
698
- const res = await this.getPool().query(query, [tableName]);
1206
+ const res = await this.getPool().query(query, [
1207
+ tableName,
1208
+ this.currentSchema
1209
+ ]);
699
1210
  const enumMap = /* @__PURE__ */ new Map();
1211
+ try {
1212
+ const nativeEnumQuery = `
1213
+ SELECT
1214
+ a.attname AS column_name,
1215
+ t.typname AS enum_name,
1216
+ e.enumlabel AS enum_value
1217
+ FROM pg_attribute a
1218
+ JOIN pg_class c ON a.attrelid = c.oid
1219
+ JOIN pg_namespace nc ON nc.oid = c.relnamespace
1220
+ JOIN pg_type t ON a.atttypid = t.oid
1221
+ JOIN pg_enum e ON t.oid = e.enumtypid
1222
+ WHERE c.relname = $1
1223
+ AND nc.nspname = $2
1224
+ AND a.attnum > 0
1225
+ AND NOT a.attisdropped
1226
+ ORDER BY a.attnum, e.enumsortorder;
1227
+ `;
1228
+ const nativeEnumRes = await this.getPool().query(nativeEnumQuery, [
1229
+ tableName,
1230
+ this.currentSchema
1231
+ ]);
1232
+ for (const r of nativeEnumRes.rows) {
1233
+ const colName = r.column_name;
1234
+ if (!enumMap.has(colName)) {
1235
+ enumMap.set(colName, []);
1236
+ }
1237
+ enumMap.get(colName).push(r.enum_value);
1238
+ }
1239
+ } catch {
1240
+ }
700
1241
  try {
701
1242
  const checkQuery = `
702
1243
  SELECT cc.check_clause
@@ -704,16 +1245,19 @@ var init_postgres = __esm({
704
1245
  JOIN information_schema.table_constraints tc
705
1246
  ON cc.constraint_name = tc.constraint_name
706
1247
  AND cc.constraint_schema = tc.constraint_schema
707
- WHERE tc.table_name = $1 AND tc.table_schema = 'public';
1248
+ WHERE tc.table_name = $1 AND tc.table_schema = $2;
708
1249
  `;
709
- const checkRes = await this.getPool().query(checkQuery, [tableName]);
1250
+ const checkRes = await this.getPool().query(checkQuery, [
1251
+ tableName,
1252
+ this.currentSchema
1253
+ ]);
710
1254
  for (const r of checkRes.rows) {
711
1255
  const inRegex = /["'`]?(\w+)["'`]?\s+(?:COLLATE\s+\w+\s+)?IN\s*\(([^)]+)\)/gi;
712
1256
  let inMatch;
713
1257
  while ((inMatch = inRegex.exec(r.check_clause)) !== null) {
714
1258
  const colName = inMatch[1];
715
1259
  const values = inMatch[2].split(",").map((s) => s.trim().replace(/^['"`]|['"`]$/g, "")).filter((s) => s.length > 0);
716
- if (values.length > 0) {
1260
+ if (values.length > 0 && !enumMap.has(colName)) {
717
1261
  enumMap.set(colName, values);
718
1262
  }
719
1263
  }
@@ -731,13 +1275,22 @@ var init_postgres = __esm({
731
1275
  onUpdate: col.fk_on_update && col.fk_on_update.toUpperCase() !== "NO ACTION" ? col.fk_on_update.toUpperCase() : void 0
732
1276
  };
733
1277
  }
1278
+ let colType = col.data_type;
1279
+ if (col.data_type === "USER-DEFINED" && col.udt_name) {
1280
+ colType = col.udt_name;
1281
+ }
734
1282
  return {
735
1283
  name: col.column_name,
736
- type: col.data_type,
1284
+ type: colType,
737
1285
  isPk: parseInt(col.is_pk) > 0,
738
1286
  nullable: parseInt(col.is_pk) > 0 ? false : col.is_nullable === "YES",
739
1287
  isUnique: parseInt(col.is_pk) > 0 || parseInt(col.is_unique) > 0,
740
- defaultValue: col.column_default != null ? String(col.column_default) : void 0,
1288
+ defaultValue: (() => {
1289
+ if (col.column_default == null) return void 0;
1290
+ const raw = String(col.column_default).trim();
1291
+ const castMatch = raw.match(/^('[\s\S]*')(?:::[\w\s()]+)$/);
1292
+ return castMatch ? castMatch[1] : raw;
1293
+ })(),
741
1294
  enumValues: enumMap.get(col.column_name),
742
1295
  fkTarget
743
1296
  };
@@ -751,22 +1304,22 @@ var init_postgres = __esm({
751
1304
  ix.indisunique as is_unique,
752
1305
  ix.indisprimary as is_primary
753
1306
  FROM
754
- pg_class t,
755
- pg_class i,
756
- pg_index ix,
757
- pg_attribute a
1307
+ pg_class t
1308
+ JOIN pg_namespace n ON n.oid = t.relnamespace
1309
+ JOIN pg_index ix ON t.oid = ix.indrelid
1310
+ JOIN pg_class i ON i.oid = ix.indexrelid
1311
+ JOIN pg_attribute a ON a.attrelid = t.oid AND a.attnum = ANY(ix.indkey)
758
1312
  WHERE
759
- t.oid = ix.indrelid
760
- AND i.oid = ix.indexrelid
761
- AND a.attrelid = t.oid
762
- AND a.attnum = ANY(ix.indkey)
763
- AND t.relkind = 'r'
1313
+ t.relkind = 'r'
764
1314
  AND t.relname = $1
765
- AND t.relnamespace = 'public'::regnamespace
1315
+ AND n.nspname = $2
766
1316
  ORDER BY
767
1317
  i.relname, array_position(ix.indkey, a.attnum);
768
1318
  `;
769
- const res = await this.getPool().query(query, [tableName]);
1319
+ const res = await this.getPool().query(query, [
1320
+ tableName,
1321
+ this.currentSchema
1322
+ ]);
770
1323
  const rows = res.rows;
771
1324
  const indexMap = /* @__PURE__ */ new Map();
772
1325
  for (const row of rows) {
@@ -786,7 +1339,7 @@ var init_postgres = __esm({
786
1339
  async getData(tableName, limit = 50, offset = 0, whereClause, orderBy) {
787
1340
  const schema = await this.getSchema(tableName);
788
1341
  const columns = schema.map((col) => col.name);
789
- let sql = `SELECT * FROM ${this.quoteIdentifier(tableName)}`;
1342
+ let sql = `SELECT * FROM ${this.quoteTable(tableName)}`;
790
1343
  if (whereClause) {
791
1344
  sql += ` WHERE ${whereClause}`;
792
1345
  }
@@ -828,7 +1381,7 @@ var init_postgres = __esm({
828
1381
  await client.query("BEGIN");
829
1382
  for (const row of rows) {
830
1383
  const placeholders = cols.map((_, i) => `$${i + 1}`).join(", ");
831
- const sql = `INSERT INTO ${this.quoteIdentifier(tableName)} (${colsQuoted}) VALUES (${placeholders})`;
1384
+ const sql = `INSERT INTO ${this.quoteTable(tableName)} (${colsQuoted}) VALUES (${placeholders})`;
832
1385
  const values = cols.map((c) => row[c]);
833
1386
  await client.query(sql, values);
834
1387
  }
@@ -840,12 +1393,83 @@ var init_postgres = __esm({
840
1393
  client.release();
841
1394
  }
842
1395
  }
843
- async truncateTable(tableName) {
844
- const quoted = this.quoteIdentifier(tableName);
845
- await this.getPool().query(
846
- `TRUNCATE TABLE ${quoted} RESTART IDENTITY CASCADE;`
847
- );
848
- }
1396
+ async truncateTable(tableName) {
1397
+ const quoted = this.quoteTable(tableName);
1398
+ await this.getPool().query(
1399
+ `TRUNCATE TABLE ${quoted} RESTART IDENTITY CASCADE;`
1400
+ );
1401
+ }
1402
+ /**
1403
+ * Quote a table name with schema prefix, e.g. "zen_stream"."users".
1404
+ * When the schema is 'public', only the table name is quoted for simplicity.
1405
+ */
1406
+ quoteTable(tableName) {
1407
+ if (this.currentSchema === "public") {
1408
+ return this.quoteIdentifier(tableName);
1409
+ }
1410
+ return `${this.quoteIdentifier(this.currentSchema)}.${this.quoteIdentifier(tableName)}`;
1411
+ }
1412
+ /**
1413
+ * List all user-accessible schemas (excluding internal PostgreSQL schemas).
1414
+ */
1415
+ async getSchemas() {
1416
+ const query = `
1417
+ SELECT schema_name
1418
+ FROM information_schema.schemata
1419
+ WHERE schema_name NOT IN ('pg_catalog', 'information_schema', 'pg_toast')
1420
+ AND schema_name NOT LIKE 'pg_temp_%'
1421
+ AND schema_name NOT LIKE 'pg_toast_temp_%'
1422
+ ORDER BY schema_name;
1423
+ `;
1424
+ const res = await this.getPool().query(query);
1425
+ return res.rows.map((row) => row.schema_name);
1426
+ }
1427
+ getCurrentSchema() {
1428
+ return this.currentSchema;
1429
+ }
1430
+ async setSchema(schema) {
1431
+ this.currentSchema = schema;
1432
+ if (this.pool) {
1433
+ await this.pool.end();
1434
+ this.pool = null;
1435
+ }
1436
+ }
1437
+ /**
1438
+ * List all user-defined enum types in the current schema or public.
1439
+ * Prioritizes enums in the current active schema over public.
1440
+ */
1441
+ async getCustomEnums() {
1442
+ const query = `
1443
+ SELECT
1444
+ t.typname AS name,
1445
+ n.nspname AS schema_name,
1446
+ array_agg(e.enumlabel ORDER BY e.enumsortorder) AS values
1447
+ FROM pg_type t
1448
+ JOIN pg_enum e ON t.oid = e.enumtypid
1449
+ JOIN pg_namespace n ON n.oid = t.typnamespace
1450
+ WHERE n.nspname = $1 OR n.nspname = 'public'
1451
+ GROUP BY t.oid, t.typname, n.nspname
1452
+ ORDER BY (CASE WHEN n.nspname = $1 THEN 0 ELSE 1 END), t.typname;
1453
+ `;
1454
+ try {
1455
+ const res = await this.getPool().query(query, [this.currentSchema]);
1456
+ const seen = /* @__PURE__ */ new Set();
1457
+ const result = [];
1458
+ for (const row of res.rows) {
1459
+ const lower = row.name.toLowerCase();
1460
+ if (!seen.has(lower)) {
1461
+ seen.add(lower);
1462
+ result.push({
1463
+ name: row.name,
1464
+ values: Array.isArray(row.values) ? row.values : []
1465
+ });
1466
+ }
1467
+ }
1468
+ return result;
1469
+ } catch {
1470
+ return [];
1471
+ }
1472
+ }
849
1473
  };
850
1474
  }
851
1475
  });
@@ -866,7 +1490,8 @@ var init_mysql = __esm({
866
1490
  if (!this.pool) {
867
1491
  this.pool = mysql.createPool({
868
1492
  uri: this.connection,
869
- multipleStatements: true
1493
+ multipleStatements: true,
1494
+ connectTimeout: 1e4
870
1495
  });
871
1496
  this.pool.on("connection", (connection) => {
872
1497
  connection.query("SET SESSION sql_mode = 'ANSI_QUOTES'").catch(() => {
@@ -1032,331 +1657,106 @@ var init_mysql = __esm({
1032
1657
  const pool = await this.getPool();
1033
1658
  const schema = await this.getSchema(tableName);
1034
1659
  const columns = schema.map((col) => col.name);
1035
- let sql = `SELECT * FROM ${this.quoteIdentifier(tableName)}`;
1036
- if (whereClause) {
1037
- sql += ` WHERE ${whereClause}`;
1038
- }
1039
- if (orderBy) {
1040
- sql += ` ORDER BY ${this.quoteIdentifier(orderBy.col)} ${orderBy.asc ? "ASC" : "DESC"}`;
1041
- }
1042
- const [rows] = await pool.query(sql + " LIMIT ? OFFSET ?", [
1043
- limit,
1044
- offset
1045
- ]);
1046
- return { columns, rows };
1047
- }
1048
- async query(sql) {
1049
- const pool = await this.getPool();
1050
- const [rows, fields] = await pool.query(sql);
1051
- let columns = [];
1052
- let data = [];
1053
- let affectedRows = void 0;
1054
- if (fields && Array.isArray(fields)) {
1055
- columns = fields.map((f) => f.name);
1056
- data = rows;
1057
- } else {
1058
- columns = ["Result"];
1059
- const count = rows.affectedRows ?? 0;
1060
- affectedRows = count;
1061
- data = [{ Result: "Success", AffectedRows: count }];
1062
- }
1063
- return { columns, rows: data, affectedRows };
1064
- }
1065
- async executeSql(sql) {
1066
- const pool = await this.getPool();
1067
- await pool.query(sql);
1068
- }
1069
- async close() {
1070
- if (this.pool) {
1071
- await this.pool.end();
1072
- this.pool = null;
1073
- }
1074
- }
1075
- async insert(tableName, rows) {
1076
- if (rows.length === 0) return;
1077
- const pool = await this.getPool();
1078
- const connection = await pool.getConnection();
1079
- try {
1080
- const cols = Object.keys(rows[0]);
1081
- const colsQuoted = cols.map((c) => this.quoteIdentifier(c)).join(", ");
1082
- const placeholders = cols.map(() => "?").join(", ");
1083
- const sql = `INSERT INTO ${this.quoteIdentifier(tableName)} (${colsQuoted}) VALUES (${placeholders})`;
1084
- await connection.beginTransaction();
1085
- for (const row of rows) {
1086
- const values = cols.map((c) => row[c]);
1087
- await connection.query(sql, values);
1088
- }
1089
- await connection.commit();
1090
- } catch (e) {
1091
- await connection.rollback();
1092
- throw e;
1093
- } finally {
1094
- connection.release();
1095
- }
1096
- }
1097
- async truncateTable(tableName) {
1098
- const quoted = this.quoteIdentifier(tableName);
1099
- await this.executeSql(`TRUNCATE TABLE ${quoted};`);
1100
- }
1101
- };
1102
- }
1103
- });
1104
-
1105
- // src/logic/adapters/index.ts
1106
- var init_adapters = __esm({
1107
- "src/logic/adapters/index.ts"() {
1108
- "use strict";
1109
- init_sqlite();
1110
- init_postgres();
1111
- init_mysql();
1112
- }
1113
- });
1114
-
1115
- // src/logic/factory.ts
1116
- function createDBAdapter(config) {
1117
- switch (config.type) {
1118
- case "sqlite":
1119
- return new SqliteAdapter(config.targetUrl);
1120
- case "postgres":
1121
- return new PostgresAdapter(config.targetUrl);
1122
- case "mysql":
1123
- return new MysqlAdapter(config.targetUrl);
1124
- default:
1125
- throw new Error(`Unsupported database type: ${config.type}`);
1126
- }
1127
- }
1128
- var init_factory = __esm({
1129
- "src/logic/factory.ts"() {
1130
- "use strict";
1131
- init_adapters();
1132
- }
1133
- });
1134
-
1135
- // src/logic/dialect.ts
1136
- function getDialect(type) {
1137
- switch (type) {
1138
- case "sqlite":
1139
- return new SqliteDialect();
1140
- case "postgres":
1141
- return new PostgresDialect();
1142
- case "mysql":
1143
- return new MysqlDialect();
1144
- default:
1145
- return new SqliteDialect();
1146
- }
1147
- }
1148
- function buildSearchWhereClause(adapter, dbType, columns, searchInput) {
1149
- const searchVal = (searchInput || "").trim();
1150
- if (!searchVal) return "";
1151
- const isSqlCondition = /[=<>]|LIKE|IN|AND|OR/i.test(searchVal);
1152
- if (isSqlCondition) {
1153
- return searchVal;
1154
- }
1155
- const strCols = columns.filter((c) => {
1156
- const t = c.type.toLowerCase();
1157
- return t.includes("char") || t.includes("text") || t.includes("string") || t.includes("uuid");
1158
- });
1159
- if (strCols.length > 0) {
1160
- const likeOp = dbType === "postgres" ? "ILIKE" : "LIKE";
1161
- const escaped = searchVal.replace(/'/g, "''");
1162
- const conditions = strCols.map(
1163
- (c) => `${adapter.quoteIdentifier(c.name)} ${likeOp} '%${escaped}%'`
1164
- );
1165
- return conditions.join(" OR ");
1166
- }
1167
- if (columns.length > 0) {
1168
- const col = columns[0];
1169
- const escaped = searchVal.replace(/'/g, "''");
1170
- return `${adapter.quoteIdentifier(col.name)} = '${escaped}'`;
1171
- }
1172
- return "";
1173
- }
1174
- var SqliteDialect, PostgresDialect, MysqlDialect;
1175
- var init_dialect = __esm({
1176
- "src/logic/dialect.ts"() {
1177
- "use strict";
1178
- SqliteDialect = class {
1179
- quoteIdentifier(name) {
1180
- return `"${name}"`;
1181
- }
1182
- escapeString(val) {
1183
- return val.replace(/'/g, "''");
1184
- }
1185
- buildCreateTable(tableName, columns) {
1186
- const lines = columns.map((col) => {
1187
- const isPk = !!(col.isPk || col.primaryKey);
1188
- const tLower = col.type.toLowerCase();
1189
- let typeStr = col.type;
1190
- if (tLower === "integer" || tLower === "int") typeStr = "INTEGER";
1191
- else if (tLower === "text" || tLower === "string") typeStr = "TEXT";
1192
- else if (tLower === "boolean" || tLower === "bool")
1193
- typeStr = "BOOLEAN";
1194
- else if (tLower === "decimal" || tLower === "numeric" || tLower === "float" || tLower === "double")
1195
- typeStr = "REAL";
1196
- else if (tLower === "datetime" || tLower === "timestamp")
1197
- typeStr = "DATETIME";
1198
- else if (col.enumValues && col.enumValues.length > 0) {
1199
- typeStr = `TEXT CHECK(${this.quoteIdentifier(col.name)} IN (${col.enumValues.map((v) => `'${this.escapeString(v)}'`).join(", ")}))`;
1200
- }
1201
- if (isPk && (tLower.includes("int") || typeStr === "INTEGER")) {
1202
- typeStr = "INTEGER PRIMARY KEY AUTOINCREMENT";
1203
- } else if (isPk) {
1204
- typeStr += " PRIMARY KEY";
1205
- } else {
1206
- if (!col.nullable) typeStr += " NOT NULL";
1207
- if (col.isUnique) typeStr += " UNIQUE";
1208
- }
1209
- if (col.defaultValue && col.defaultValue !== "AutoInc" && !col.defaultValue.startsWith("FK ->")) {
1210
- if (col.defaultValue === "Timestamp") {
1211
- typeStr += " DEFAULT CURRENT_TIMESTAMP";
1212
- } else {
1213
- typeStr += ` DEFAULT ${col.defaultValue}`;
1214
- }
1215
- }
1216
- return ` ${this.quoteIdentifier(col.name)} ${typeStr}`;
1217
- });
1218
- const fks = columns.filter(
1219
- (col) => col.fkTarget && col.fkTarget.table && col.fkTarget.column
1220
- ).map((col) => {
1221
- let fkStr = ` FOREIGN KEY (${this.quoteIdentifier(col.name)}) REFERENCES ${this.quoteIdentifier(col.fkTarget.table)}(${this.quoteIdentifier(col.fkTarget.column)})`;
1222
- if (col.fkTarget.onDelete && col.fkTarget.onDelete !== "NO ACTION") {
1223
- fkStr += ` ON DELETE ${col.fkTarget.onDelete}`;
1224
- }
1225
- if (col.fkTarget.onUpdate && col.fkTarget.onUpdate !== "NO ACTION") {
1226
- fkStr += ` ON UPDATE ${col.fkTarget.onUpdate}`;
1227
- }
1228
- return fkStr;
1229
- });
1230
- if (fks.length > 0) {
1231
- lines.push(...fks);
1232
- }
1233
- return `CREATE TABLE ${this.quoteIdentifier(tableName)} (
1234
- ${lines.join(",\n")}
1235
- );`;
1236
- }
1237
- };
1238
- PostgresDialect = class {
1239
- quoteIdentifier(name) {
1240
- return `"${name}"`;
1241
- }
1242
- escapeString(val) {
1243
- return val.replace(/'/g, "''");
1244
- }
1245
- buildCreateTable(tableName, columns) {
1246
- const lines = columns.map((col) => {
1247
- const isPk = !!(col.isPk || col.primaryKey);
1248
- const tLower = col.type.toLowerCase();
1249
- let typeStr = col.type;
1250
- if (isPk && (tLower === "integer" || tLower === "int")) {
1251
- typeStr = "SERIAL PRIMARY KEY";
1252
- } else {
1253
- if (tLower === "integer" || tLower === "int") typeStr = "INTEGER";
1254
- else if (tLower === "text" || tLower === "string") typeStr = "TEXT";
1255
- else if (tLower === "boolean" || tLower === "bool")
1256
- typeStr = "BOOLEAN";
1257
- else if (tLower === "decimal" || tLower === "numeric")
1258
- typeStr = "NUMERIC";
1259
- else if (tLower === "datetime" || tLower === "timestamp")
1260
- typeStr = "TIMESTAMP";
1261
- else if (col.enumValues && col.enumValues.length > 0) {
1262
- typeStr = `VARCHAR(255) CHECK(${this.quoteIdentifier(col.name)} IN (${col.enumValues.map((v) => `'${this.escapeString(v)}'`).join(", ")}))`;
1263
- }
1264
- if (isPk) typeStr += " PRIMARY KEY";
1265
- if (!col.nullable && !isPk) typeStr += " NOT NULL";
1266
- if (col.isUnique && !isPk) typeStr += " UNIQUE";
1267
- }
1268
- if (col.defaultValue && col.defaultValue !== "AutoInc" && !col.defaultValue.startsWith("FK ->")) {
1269
- if (col.defaultValue === "Timestamp") {
1270
- typeStr += " DEFAULT CURRENT_TIMESTAMP";
1271
- } else {
1272
- typeStr += ` DEFAULT ${col.defaultValue}`;
1273
- }
1274
- }
1275
- return ` ${this.quoteIdentifier(col.name)} ${typeStr}`;
1276
- });
1277
- const fks = columns.filter(
1278
- (col) => col.fkTarget && col.fkTarget.table && col.fkTarget.column
1279
- ).map((col) => {
1280
- let fkStr = ` FOREIGN KEY (${this.quoteIdentifier(col.name)}) REFERENCES ${this.quoteIdentifier(col.fkTarget.table)}(${this.quoteIdentifier(col.fkTarget.column)})`;
1281
- if (col.fkTarget.onDelete && col.fkTarget.onDelete !== "NO ACTION") {
1282
- fkStr += ` ON DELETE ${col.fkTarget.onDelete}`;
1283
- }
1284
- if (col.fkTarget.onUpdate && col.fkTarget.onUpdate !== "NO ACTION") {
1285
- fkStr += ` ON UPDATE ${col.fkTarget.onUpdate}`;
1286
- }
1287
- return fkStr;
1288
- });
1289
- if (fks.length > 0) {
1290
- lines.push(...fks);
1660
+ let sql = `SELECT * FROM ${this.quoteIdentifier(tableName)}`;
1661
+ if (whereClause) {
1662
+ sql += ` WHERE ${whereClause}`;
1291
1663
  }
1292
- return `CREATE TABLE ${this.quoteIdentifier(tableName)} (
1293
- ${lines.join(",\n")}
1294
- );`;
1664
+ if (orderBy) {
1665
+ sql += ` ORDER BY ${this.quoteIdentifier(orderBy.col)} ${orderBy.asc ? "ASC" : "DESC"}`;
1666
+ }
1667
+ const [rows] = await pool.query(sql + " LIMIT ? OFFSET ?", [
1668
+ limit,
1669
+ offset
1670
+ ]);
1671
+ return { columns, rows };
1295
1672
  }
1296
- };
1297
- MysqlDialect = class {
1298
- quoteIdentifier(name) {
1299
- return `\`${name}\``;
1673
+ async query(sql) {
1674
+ const pool = await this.getPool();
1675
+ const [rows, fields] = await pool.query(sql);
1676
+ let columns = [];
1677
+ let data = [];
1678
+ let affectedRows = void 0;
1679
+ if (fields && Array.isArray(fields)) {
1680
+ columns = fields.map((f) => f.name);
1681
+ data = rows;
1682
+ } else {
1683
+ columns = ["Result"];
1684
+ const count = rows.affectedRows ?? 0;
1685
+ affectedRows = count;
1686
+ data = [{ Result: "Success", AffectedRows: count }];
1687
+ }
1688
+ return { columns, rows: data, affectedRows };
1300
1689
  }
1301
- escapeString(val) {
1302
- return val.replace(/'/g, "''");
1690
+ async executeSql(sql) {
1691
+ const pool = await this.getPool();
1692
+ await pool.query(sql);
1303
1693
  }
1304
- buildCreateTable(tableName, columns) {
1305
- const lines = columns.map((col) => {
1306
- const isPk = !!(col.isPk || col.primaryKey);
1307
- const tLower = col.type.toLowerCase();
1308
- let typeStr = col.type;
1309
- if (tLower === "integer" || tLower === "int") typeStr = "INT";
1310
- else if (tLower === "text" || tLower === "string")
1311
- typeStr = "VARCHAR(255)";
1312
- else if (tLower === "boolean" || tLower === "bool")
1313
- typeStr = "BOOLEAN";
1314
- else if (tLower === "decimal" || tLower === "numeric")
1315
- typeStr = "DOUBLE";
1316
- else if (tLower === "datetime" || tLower === "timestamp")
1317
- typeStr = "DATETIME";
1318
- else if (col.enumValues && col.enumValues.length > 0) {
1319
- typeStr = `VARCHAR(255) CHECK(${this.quoteIdentifier(col.name)} IN (${col.enumValues.map((v) => `'${this.escapeString(v)}'`).join(", ")}))`;
1320
- }
1321
- if (isPk && (tLower.includes("int") || typeStr === "INT")) {
1322
- typeStr += " AUTO_INCREMENT PRIMARY KEY";
1323
- } else if (isPk) {
1324
- typeStr += " PRIMARY KEY";
1325
- }
1326
- if (!col.nullable && !isPk) typeStr += " NOT NULL";
1327
- if (col.isUnique && !isPk) typeStr += " UNIQUE";
1328
- if (col.defaultValue && col.defaultValue !== "AutoInc" && !col.defaultValue.startsWith("FK ->")) {
1329
- if (col.defaultValue === "Timestamp") {
1330
- typeStr += " DEFAULT CURRENT_TIMESTAMP";
1331
- } else {
1332
- typeStr += ` DEFAULT ${col.defaultValue}`;
1333
- }
1334
- }
1335
- return ` ${this.quoteIdentifier(col.name)} ${typeStr}`;
1336
- });
1337
- const fks = columns.filter(
1338
- (col) => col.fkTarget && col.fkTarget.table && col.fkTarget.column
1339
- ).map((col) => {
1340
- let fkStr = ` FOREIGN KEY (${this.quoteIdentifier(col.name)}) REFERENCES ${this.quoteIdentifier(col.fkTarget.table)}(${this.quoteIdentifier(col.fkTarget.column)})`;
1341
- if (col.fkTarget.onDelete && col.fkTarget.onDelete !== "NO ACTION") {
1342
- fkStr += ` ON DELETE ${col.fkTarget.onDelete}`;
1343
- }
1344
- if (col.fkTarget.onUpdate && col.fkTarget.onUpdate !== "NO ACTION") {
1345
- fkStr += ` ON UPDATE ${col.fkTarget.onUpdate}`;
1694
+ async close() {
1695
+ if (this.pool) {
1696
+ await this.pool.end();
1697
+ this.pool = null;
1698
+ }
1699
+ }
1700
+ async insert(tableName, rows) {
1701
+ if (rows.length === 0) return;
1702
+ const pool = await this.getPool();
1703
+ const connection = await pool.getConnection();
1704
+ try {
1705
+ const cols = Object.keys(rows[0]);
1706
+ const colsQuoted = cols.map((c) => this.quoteIdentifier(c)).join(", ");
1707
+ const placeholders = cols.map(() => "?").join(", ");
1708
+ const sql = `INSERT INTO ${this.quoteIdentifier(tableName)} (${colsQuoted}) VALUES (${placeholders})`;
1709
+ await connection.beginTransaction();
1710
+ for (const row of rows) {
1711
+ const values = cols.map((c) => row[c]);
1712
+ await connection.query(sql, values);
1346
1713
  }
1347
- return fkStr;
1348
- });
1349
- if (fks.length > 0) {
1350
- lines.push(...fks);
1714
+ await connection.commit();
1715
+ } catch (e) {
1716
+ await connection.rollback();
1717
+ throw e;
1718
+ } finally {
1719
+ connection.release();
1351
1720
  }
1352
- return `CREATE TABLE ${this.quoteIdentifier(tableName)} (
1353
- ${lines.join(",\n")}
1354
- );`;
1721
+ }
1722
+ async truncateTable(tableName) {
1723
+ const quoted = this.quoteIdentifier(tableName);
1724
+ await this.executeSql(`TRUNCATE TABLE ${quoted};`);
1355
1725
  }
1356
1726
  };
1357
1727
  }
1358
1728
  });
1359
1729
 
1730
+ // src/logic/adapters/index.ts
1731
+ var init_adapters = __esm({
1732
+ "src/logic/adapters/index.ts"() {
1733
+ "use strict";
1734
+ init_sqlite();
1735
+ init_postgres();
1736
+ init_mysql();
1737
+ }
1738
+ });
1739
+
1740
+ // src/logic/factory.ts
1741
+ function createDBAdapter(config) {
1742
+ switch (config.type) {
1743
+ case "sqlite":
1744
+ return new SqliteAdapter(config.targetUrl);
1745
+ case "postgres":
1746
+ return new PostgresAdapter(config.targetUrl);
1747
+ case "mysql":
1748
+ return new MysqlAdapter(config.targetUrl);
1749
+ default:
1750
+ throw new Error(`Unsupported database type: ${config.type}`);
1751
+ }
1752
+ }
1753
+ var init_factory = __esm({
1754
+ "src/logic/factory.ts"() {
1755
+ "use strict";
1756
+ init_adapters();
1757
+ }
1758
+ });
1759
+
1360
1760
  // src/logic/mutation.ts
1361
1761
  async function batchMutateTableData(adapter, dbType, options) {
1362
1762
  const {
@@ -1461,12 +1861,18 @@ async function batchMutateTableData(adapter, dbType, options) {
1461
1861
  }
1462
1862
  let executedCount = 0;
1463
1863
  try {
1864
+ await adapter.executeSql("BEGIN;");
1464
1865
  for (const sql of sqls) {
1465
1866
  await adapter.executeSql(sql);
1466
1867
  executedCount++;
1467
1868
  }
1869
+ await adapter.executeSql("COMMIT;");
1468
1870
  return ok({ modifiedCount: executedCount, executedCount });
1469
1871
  } catch (e) {
1872
+ try {
1873
+ await adapter.executeSql("ROLLBACK;");
1874
+ } catch {
1875
+ }
1470
1876
  return err(
1471
1877
  e.message || "Failed to execute database mutations",
1472
1878
  void 0,
@@ -1485,6 +1891,33 @@ var init_mutation = __esm({
1485
1891
  // src/logic/schema.ts
1486
1892
  async function createTable(adapter, dbType, tableName, columns) {
1487
1893
  try {
1894
+ if (dbType === "postgres") {
1895
+ const processedEnums = /* @__PURE__ */ new Set();
1896
+ for (const col of columns) {
1897
+ if (col.enumValues && col.enumValues.length > 0 && col.type && !["enum", "varchar", "varchar(255)", "text", "string"].includes(
1898
+ col.type.toLowerCase()
1899
+ ) && !processedEnums.has(col.type.toLowerCase())) {
1900
+ processedEnums.add(col.type.toLowerCase());
1901
+ const vals = col.enumValues.map((v) => `'${v.replace(/'/g, "''")}'`).join(", ");
1902
+ const enumTypeQuoted = adapter.quoteIdentifier(col.type);
1903
+ const escapedName = col.type.replace(/'/g, "''");
1904
+ const createTypeSql = `
1905
+ DO $$
1906
+ BEGIN
1907
+ IF NOT EXISTS (
1908
+ SELECT 1 FROM pg_type t
1909
+ JOIN pg_namespace n ON n.oid = t.typnamespace
1910
+ WHERE t.typname = '${escapedName}'
1911
+ AND (n.nspname = current_schema() OR n.nspname = 'public')
1912
+ ) THEN
1913
+ CREATE TYPE ${enumTypeQuoted} AS ENUM (${vals});
1914
+ END IF;
1915
+ END$$;
1916
+ `;
1917
+ await adapter.executeSql(createTypeSql);
1918
+ }
1919
+ }
1920
+ }
1488
1921
  const dialect = getDialect(dbType);
1489
1922
  const sql = dialect.buildCreateTable(tableName, columns);
1490
1923
  await adapter.executeSql(sql);
@@ -1575,6 +2008,7 @@ async function applySchemaChanges(adapter, dbType, options) {
1575
2008
  let nullable = edits.nullable !== void 0 ? !!edits.nullable : col.nullable;
1576
2009
  let defaultValue = edits.defaultValue !== void 0 ? edits.defaultValue : col.defaultValue;
1577
2010
  let isUnique = edits.isUnique !== void 0 ? !!edits.isUnique : col.isPk && !isPk ? false : !!col.isUnique;
2011
+ let enumValues = edits.enumValues !== void 0 ? edits.enumValues : col.enumValues;
1578
2012
  if (edits.name && edits.name !== col.name) {
1579
2013
  renames[col.name] = edits.name;
1580
2014
  }
@@ -1585,7 +2019,8 @@ async function applySchemaChanges(adapter, dbType, options) {
1585
2019
  nullable,
1586
2020
  defaultValue,
1587
2021
  isUnique,
1588
- fkTarget
2022
+ fkTarget,
2023
+ enumValues
1589
2024
  });
1590
2025
  }
1591
2026
  for (const ins of pendingInserts) {
@@ -1620,7 +2055,8 @@ async function applySchemaChanges(adapter, dbType, options) {
1620
2055
  nullable: ins.nullable !== void 0 ? !!ins.nullable : true,
1621
2056
  defaultValue: ins.defaultValue,
1622
2057
  isUnique: !!ins.isUnique,
1623
- fkTarget
2058
+ fkTarget,
2059
+ enumValues: ins.enumValues
1624
2060
  });
1625
2061
  }
1626
2062
  targetColumns = updated;
@@ -1678,8 +2114,32 @@ async function applySchemaChanges(adapter, dbType, options) {
1678
2114
  const quotedCol = dialect.quoteIdentifier(currentName);
1679
2115
  const type = edits.type || "TEXT";
1680
2116
  if (dbType === "postgres") {
2117
+ let pgType = type;
2118
+ if (edits.enumValues && edits.enumValues.length > 0) {
2119
+ if (edits.isNewEnum && edits.type) {
2120
+ const escapedName = edits.type.replace(/'/g, "''");
2121
+ const vals = edits.enumValues.map((v) => `'${dialect.escapeString(v)}'`).join(", ");
2122
+ const quotedType = dialect.quoteIdentifier(edits.type);
2123
+ sqls.push(`
2124
+ DO $$
2125
+ BEGIN
2126
+ IF NOT EXISTS (
2127
+ SELECT 1 FROM pg_type t
2128
+ JOIN pg_namespace n ON n.oid = t.typnamespace
2129
+ WHERE t.typname = '${escapedName}'
2130
+ AND (n.nspname = current_schema() OR n.nspname = 'public')
2131
+ ) THEN
2132
+ CREATE TYPE ${quotedType} AS ENUM (${vals});
2133
+ END IF;
2134
+ END$$;
2135
+ `);
2136
+ pgType = quotedType;
2137
+ } else if (edits.type) {
2138
+ pgType = dialect.quoteIdentifier(edits.type);
2139
+ }
2140
+ }
1681
2141
  sqls.push(
1682
- `ALTER TABLE ${quotedTable} ALTER COLUMN ${quotedCol} TYPE ${type};`
2142
+ `ALTER TABLE ${quotedTable} ALTER COLUMN ${quotedCol} TYPE ${pgType} USING ${quotedCol}::text::${pgType};`
1683
2143
  );
1684
2144
  if (edits.nullable === false) {
1685
2145
  sqls.push(
@@ -1690,17 +2150,29 @@ async function applySchemaChanges(adapter, dbType, options) {
1690
2150
  `ALTER TABLE ${quotedTable} ALTER COLUMN ${quotedCol} DROP NOT NULL;`
1691
2151
  );
1692
2152
  }
1693
- if (edits.defaultValue) {
1694
- const escapedDef = dialect.escapeString(edits.defaultValue);
1695
- sqls.push(
1696
- `ALTER TABLE ${quotedTable} ALTER COLUMN ${quotedCol} SET DEFAULT '${escapedDef}';`
1697
- );
2153
+ if (edits.defaultValue !== void 0) {
2154
+ const formatted = formatSqlDefaultValue(edits.defaultValue);
2155
+ if (formatted !== null) {
2156
+ sqls.push(
2157
+ `ALTER TABLE ${quotedTable} ALTER COLUMN ${quotedCol} SET DEFAULT ${formatted};`
2158
+ );
2159
+ } else {
2160
+ sqls.push(
2161
+ `ALTER TABLE ${quotedTable} ALTER COLUMN ${quotedCol} DROP DEFAULT;`
2162
+ );
2163
+ }
1698
2164
  }
1699
2165
  } else if (dbType === "mysql") {
2166
+ let mysqlType = type;
2167
+ if (edits.enumValues && edits.enumValues.length > 0) {
2168
+ const vals = edits.enumValues.map((v) => `'${dialect.escapeString(v)}'`).join(", ");
2169
+ mysqlType = `ENUM(${vals})`;
2170
+ }
1700
2171
  const nullStr = edits.nullable === false ? "NOT NULL" : "NULL";
1701
- const defStr = edits.defaultValue ? `DEFAULT '${dialect.escapeString(edits.defaultValue)}'` : "";
2172
+ const formatted = formatSqlDefaultValue(edits.defaultValue);
2173
+ const defStr = formatted !== null ? `DEFAULT ${formatted}` : "";
1702
2174
  sqls.push(
1703
- `ALTER TABLE ${quotedTable} MODIFY COLUMN ${quotedCol} ${type} ${nullStr} ${defStr};`
2175
+ `ALTER TABLE ${quotedTable} MODIFY COLUMN ${quotedCol} ${mysqlType} ${nullStr} ${defStr};`
1704
2176
  );
1705
2177
  }
1706
2178
  }
@@ -1708,9 +2180,36 @@ async function applySchemaChanges(adapter, dbType, options) {
1708
2180
  for (const ins of pendingInserts) {
1709
2181
  if (!ins.name || ins.name.trim() === "") continue;
1710
2182
  const quotedCol = dialect.quoteIdentifier(ins.name.trim());
1711
- const type = ins.type || "TEXT";
2183
+ let type = ins.type || "TEXT";
2184
+ if (dbType === "postgres" && ins.enumValues && ins.enumValues.length > 0) {
2185
+ if (ins.isNewEnum && ins.type) {
2186
+ const escapedName = ins.type.replace(/'/g, "''");
2187
+ const vals = ins.enumValues.map((v) => `'${dialect.escapeString(v)}'`).join(", ");
2188
+ const quotedType = dialect.quoteIdentifier(ins.type);
2189
+ sqls.push(`
2190
+ DO $$
2191
+ BEGIN
2192
+ IF NOT EXISTS (
2193
+ SELECT 1 FROM pg_type t
2194
+ JOIN pg_namespace n ON n.oid = t.typnamespace
2195
+ WHERE t.typname = '${escapedName}'
2196
+ AND (n.nspname = current_schema() OR n.nspname = 'public')
2197
+ ) THEN
2198
+ CREATE TYPE ${quotedType} AS ENUM (${vals});
2199
+ END IF;
2200
+ END$$;
2201
+ `);
2202
+ type = quotedType;
2203
+ } else if (ins.type) {
2204
+ type = dialect.quoteIdentifier(ins.type);
2205
+ }
2206
+ } else if (dbType === "mysql" && ins.enumValues && ins.enumValues.length > 0) {
2207
+ const vals = ins.enumValues.map((v) => `'${dialect.escapeString(v)}'`).join(", ");
2208
+ type = `ENUM(${vals})`;
2209
+ }
1712
2210
  const nullStr = ins.nullable === false ? "NOT NULL" : "";
1713
- const defStr = ins.defaultValue ? `DEFAULT '${dialect.escapeString(ins.defaultValue)}'` : "";
2211
+ const formattedDef = formatSqlDefaultValue(ins.defaultValue);
2212
+ const defStr = formattedDef !== null ? `DEFAULT ${formattedDef}` : "";
1714
2213
  sqls.push(
1715
2214
  `ALTER TABLE ${quotedTable} ADD COLUMN ${quotedCol} ${type} ${nullStr} ${defStr};`
1716
2215
  );
@@ -1749,7 +2248,7 @@ async function applySchemaChanges(adapter, dbType, options) {
1749
2248
  }
1750
2249
  async function getTableRowCount(adapter, tableName) {
1751
2250
  try {
1752
- const quoted = adapter.quoteIdentifier(tableName);
2251
+ const quoted = adapter.quoteTable ? adapter.quoteTable(tableName) : adapter.quoteIdentifier(tableName);
1753
2252
  const countRes = await adapter.query(
1754
2253
  `SELECT COUNT(*) as cnt FROM ${quoted}`
1755
2254
  );
@@ -1921,6 +2420,43 @@ async function exportDatabaseSchemaDdl(adapter, dbType) {
1921
2420
  );
1922
2421
  }
1923
2422
  }
2423
+ async function getDatabaseEnums(adapter) {
2424
+ try {
2425
+ if (adapter.getCustomEnums) {
2426
+ const enums = await adapter.getCustomEnums();
2427
+ return ok(enums);
2428
+ }
2429
+ const enumsMap = /* @__PURE__ */ new Map();
2430
+ try {
2431
+ const tables = await adapter.getTables();
2432
+ for (const t of tables.slice(0, 30)) {
2433
+ const cols = await adapter.getSchema(t);
2434
+ for (const col of cols) {
2435
+ if (col.enumValues && col.enumValues.length > 0) {
2436
+ const key = col.type && ![
2437
+ "enum",
2438
+ "varchar",
2439
+ "varchar(255)",
2440
+ "text",
2441
+ "string"
2442
+ ].includes(col.type.toLowerCase()) ? col.type : col.name;
2443
+ if (!enumsMap.has(key)) {
2444
+ enumsMap.set(key, col.enumValues);
2445
+ }
2446
+ }
2447
+ }
2448
+ }
2449
+ } catch {
2450
+ }
2451
+ const data = Array.from(enumsMap.entries()).map(([name, values]) => ({
2452
+ name,
2453
+ values
2454
+ }));
2455
+ return ok(data);
2456
+ } catch (e) {
2457
+ return err(e.message || "Failed to get database enums", void 0, e);
2458
+ }
2459
+ }
1924
2460
  var init_schema = __esm({
1925
2461
  "src/logic/schema.ts"() {
1926
2462
  "use strict";
@@ -1984,6 +2520,9 @@ function inferColumnStrategy(col) {
1984
2520
  if (name.includes("last_name")) {
1985
2521
  return { type: "last_name", label: "Last Name" };
1986
2522
  }
2523
+ if (name.includes("username") || name.includes("user_name") || name.includes("handle")) {
2524
+ return { type: "username", label: "Username" };
2525
+ }
1987
2526
  if (name.includes("name") || name.includes("author") || name.includes("user")) {
1988
2527
  return { type: "full_name", label: "Full Name" };
1989
2528
  }
@@ -2064,6 +2603,11 @@ function generateFieldValue(strategy, fkCache = {}) {
2064
2603
  return getRandomItem(FIRST_NAMES);
2065
2604
  case "last_name":
2066
2605
  return getRandomItem(LAST_NAMES);
2606
+ case "username": {
2607
+ const f = getRandomItem(FIRST_NAMES).toLowerCase();
2608
+ const l = getRandomItem(LAST_NAMES).toLowerCase();
2609
+ return `${f}_${l}${getRandomInt(100, 99999)}`;
2610
+ }
2067
2611
  case "full_name":
2068
2612
  return `${getRandomItem(FIRST_NAMES)} ${getRandomItem(LAST_NAMES)}`;
2069
2613
  case "avatar":
@@ -2285,6 +2829,27 @@ var init_seeder = __esm({
2285
2829
  // src/logic/transfer.ts
2286
2830
  import fs2 from "fs/promises";
2287
2831
  import path2 from "path";
2832
+ async function getRestoreCandidates(dir) {
2833
+ const candidates = [];
2834
+ try {
2835
+ const entries = await fs2.readdir(dir, { withFileTypes: true });
2836
+ for (const entry of entries) {
2837
+ if (entry.isDirectory() && entry.name.startsWith("drixio_backup_")) {
2838
+ candidates.push({
2839
+ name: `\u{1F4C1} ${entry.name} (Backup Directory)`,
2840
+ value: entry.name
2841
+ });
2842
+ } else if (entry.isFile() && (entry.name.endsWith(".sql") || entry.name.endsWith(".json"))) {
2843
+ candidates.push({
2844
+ name: `\u{1F4C4} ${entry.name} (${entry.name.endsWith(".sql") ? "SQL Script" : "JSON Dump"})`,
2845
+ value: entry.name
2846
+ });
2847
+ }
2848
+ }
2849
+ } catch {
2850
+ }
2851
+ return candidates;
2852
+ }
2288
2853
  function formatCsvValue(val) {
2289
2854
  if (val === null || val === void 0) return "";
2290
2855
  const str = String(val);
@@ -2778,10 +3343,157 @@ function generateDatabaseSqlDumpStream(adapter, dbType) {
2778
3343
  encoder.encode(`-- Error generating backup: ${e}
2779
3344
  `)
2780
3345
  );
2781
- controller.close();
3346
+ controller.close();
3347
+ }
3348
+ }
3349
+ });
3350
+ }
3351
+ function nodeToWebStream(nodeStream) {
3352
+ return new ReadableStream({
3353
+ start(controller) {
3354
+ nodeStream.on("data", (chunk) => controller.enqueue(chunk));
3355
+ nodeStream.on("end", () => controller.close());
3356
+ nodeStream.on("error", (err2) => controller.error(err2));
3357
+ },
3358
+ cancel() {
3359
+ nodeStream.destroy();
3360
+ }
3361
+ });
3362
+ }
3363
+ async function exportDatabaseNative(dbType, connectionUrl, schemaOnly) {
3364
+ try {
3365
+ const executeNativeDump = (cmd, args, envName) => {
3366
+ return new Promise((resolve, reject) => {
3367
+ const cp = import("child_process").then((m) => m.spawn(cmd, args));
3368
+ let started = false;
3369
+ cp.then((cpInstance) => {
3370
+ const stream2 = nodeToWebStream(cpInstance.stdout);
3371
+ cpInstance.on("error", (e) => {
3372
+ if (!started)
3373
+ reject(
3374
+ new Error(
3375
+ `Native tool '${cmd}' not found. Please install ${envName}.`
3376
+ )
3377
+ );
3378
+ });
3379
+ setTimeout(() => {
3380
+ if (!cpInstance.killed) {
3381
+ started = true;
3382
+ resolve(stream2);
3383
+ }
3384
+ }, 100);
3385
+ });
3386
+ });
3387
+ };
3388
+ let stream;
3389
+ if (dbType === "sqlite") {
3390
+ const dbPath = connectionUrl.replace("file:", "");
3391
+ stream = await executeNativeDump(
3392
+ "sqlite3",
3393
+ [dbPath, schemaOnly ? ".schema" : ".dump"],
3394
+ "SQLite CLI"
3395
+ );
3396
+ } else if (dbType === "mysql") {
3397
+ const parsed = new URL(connectionUrl);
3398
+ const user = parsed.username;
3399
+ const pass = parsed.password;
3400
+ const host = parsed.hostname;
3401
+ const port = parsed.port || "3306";
3402
+ const dbname = parsed.pathname.substring(1);
3403
+ const args = ["-u", user, `-p${pass}`, "-h", host, "-P", port];
3404
+ if (schemaOnly) args.push("--no-data");
3405
+ args.push(dbname);
3406
+ stream = await executeNativeDump(
3407
+ "mysqldump",
3408
+ args,
3409
+ "MySQL Client"
3410
+ );
3411
+ } else if (dbType === "postgres") {
3412
+ const args = [connectionUrl];
3413
+ if (schemaOnly) args.push("--schema-only");
3414
+ stream = await executeNativeDump(
3415
+ "pg_dump",
3416
+ args,
3417
+ "PostgreSQL CLI"
3418
+ );
3419
+ } else {
3420
+ return err("Unsupported database type for native export");
3421
+ }
3422
+ return ok(stream);
3423
+ } catch (e) {
3424
+ return err(e.message || "Failed to export database using native tool", void 0, e);
3425
+ }
3426
+ }
3427
+ async function importDatabaseNative(adapter, dbType, connectionUrl, sqlContent) {
3428
+ try {
3429
+ const executeNativeImport = (cmd, args, fileContent, envName) => {
3430
+ return new Promise((resolve, reject) => {
3431
+ import("child_process").then((m) => {
3432
+ const cp = m.spawn(cmd, args);
3433
+ let started = false;
3434
+ let errStr = "";
3435
+ cp.on("error", (e) => {
3436
+ if (!started)
3437
+ reject(
3438
+ new Error(
3439
+ `Native tool '${cmd}' not found. Please install ${envName}.`
3440
+ )
3441
+ );
3442
+ });
3443
+ cp.stderr.on("data", (d) => errStr += d.toString());
3444
+ cp.on("close", (code) => {
3445
+ if (code === 0) resolve();
3446
+ else reject(new Error(`Native import failed: ${errStr}`));
3447
+ });
3448
+ cp.stdin.write(fileContent);
3449
+ cp.stdin.end();
3450
+ started = true;
3451
+ });
3452
+ });
3453
+ };
3454
+ try {
3455
+ if (dbType === "sqlite") {
3456
+ const dbPath = connectionUrl.replace("file:", "");
3457
+ await executeNativeImport(
3458
+ "sqlite3",
3459
+ [dbPath],
3460
+ sqlContent,
3461
+ "SQLite CLI"
3462
+ );
3463
+ } else if (dbType === "mysql") {
3464
+ const parsed = new URL(connectionUrl);
3465
+ const user = parsed.username;
3466
+ const pass = parsed.password;
3467
+ const host = parsed.hostname;
3468
+ const port = parsed.port || "3306";
3469
+ const dbname = parsed.pathname.substring(1);
3470
+ await executeNativeImport(
3471
+ "mysql",
3472
+ ["-u", user, `-p${pass}`, "-h", host, "-P", port, dbname],
3473
+ sqlContent,
3474
+ "MySQL Client"
3475
+ );
3476
+ } else if (dbType === "postgres") {
3477
+ await executeNativeImport(
3478
+ "psql",
3479
+ [connectionUrl],
3480
+ sqlContent,
3481
+ "PostgreSQL CLI"
3482
+ );
3483
+ } else {
3484
+ return err("Unsupported database type for native import");
3485
+ }
3486
+ } catch (e) {
3487
+ if (e.message.includes("Native tool")) {
3488
+ await adapter.executeSql(sqlContent);
3489
+ } else {
3490
+ throw e;
2782
3491
  }
2783
3492
  }
2784
- });
3493
+ return ok({ message: "Import completed successfully" });
3494
+ } catch (e) {
3495
+ return err(`Import Failed: ${e.message}`, void 0, e);
3496
+ }
2785
3497
  }
2786
3498
  var init_transfer = __esm({
2787
3499
  "src/logic/transfer.ts"() {
@@ -2842,6 +3554,10 @@ async function createDatabase(options) {
2842
3554
  });
2843
3555
  try {
2844
3556
  await conn.query(`CREATE DATABASE IF NOT EXISTS \`${dbName}\``);
3557
+ const quotedDbName = dbName.replace(/`/g, "``");
3558
+ await conn.query(
3559
+ `CREATE DATABASE IF NOT EXISTS \`${quotedDbName}\``
3560
+ );
2845
3561
  } finally {
2846
3562
  await conn.end();
2847
3563
  }
@@ -2872,6 +3588,8 @@ async function createDatabase(options) {
2872
3588
  );
2873
3589
  if (res.rowCount === 0) {
2874
3590
  await conn.query(`CREATE DATABASE "${dbName}"`);
3591
+ const quotedDbName = dbName.replace(/"/g, '""');
3592
+ await conn.query(`CREATE DATABASE "${quotedDbName}"`);
2875
3593
  }
2876
3594
  } finally {
2877
3595
  await conn.end();
@@ -2923,6 +3641,8 @@ async function dropDatabase(options) {
2923
3641
  });
2924
3642
  try {
2925
3643
  await conn.query(`DROP DATABASE IF EXISTS \`${dbName}\``);
3644
+ const quotedDbName = dbName.replace(/`/g, "``");
3645
+ await conn.query(`DROP DATABASE IF EXISTS \`${quotedDbName}\``);
2926
3646
  } finally {
2927
3647
  await conn.end();
2928
3648
  }
@@ -2949,6 +3669,8 @@ async function dropDatabase(options) {
2949
3669
  await conn.connect();
2950
3670
  try {
2951
3671
  await conn.query(`DROP DATABASE IF EXISTS "${dbName}"`);
3672
+ const quotedDbName = dbName.replace(/"/g, '""');
3673
+ await conn.query(`DROP DATABASE IF EXISTS "${quotedDbName}"`);
2952
3674
  } finally {
2953
3675
  await conn.end();
2954
3676
  }
@@ -3680,7 +4402,10 @@ function generateMigrationSql(diff, dbType) {
3680
4402
  let typeStr = col.type || "TEXT";
3681
4403
  if (col.nullable === false) typeStr += " NOT NULL";
3682
4404
  if (col.defaultValue !== void 0 && col.defaultValue !== null) {
3683
- typeStr += ` DEFAULT '${dialect.escapeString(col.defaultValue)}'`;
4405
+ const formatted = formatSqlDefaultValue(col.defaultValue);
4406
+ if (formatted !== null) {
4407
+ typeStr += ` DEFAULT ${formatted}`;
4408
+ }
3684
4409
  }
3685
4410
  lines.push(`ALTER TABLE ${qTable} ADD COLUMN ${qCol} ${typeStr};`);
3686
4411
  }
@@ -3809,7 +4534,10 @@ function generateRollbackSql(diff, dbType) {
3809
4534
  let typeStr = col.type || "TEXT";
3810
4535
  if (col.nullable === false) typeStr += " NOT NULL";
3811
4536
  if (col.defaultValue !== void 0 && col.defaultValue !== null) {
3812
- typeStr += ` DEFAULT '${dialect.escapeString(col.defaultValue)}'`;
4537
+ const formatted = formatSqlDefaultValue(col.defaultValue);
4538
+ if (formatted !== null) {
4539
+ typeStr += ` DEFAULT ${formatted}`;
4540
+ }
3813
4541
  }
3814
4542
  lines.push(`ALTER TABLE ${qTable} ADD COLUMN ${qCol} ${typeStr};`);
3815
4543
  }
@@ -4050,9 +4778,18 @@ function extractSnippetParams(sql) {
4050
4778
  function substituteSnippetParams(sql, params, dbType = "sqlite") {
4051
4779
  if (!sql) return "";
4052
4780
  const dialect = getDialect(dbType);
4781
+ const identifierKeys = /* @__PURE__ */ new Set([
4782
+ "table",
4783
+ "column",
4784
+ "child_table",
4785
+ "parent_table",
4786
+ "fk_col",
4787
+ "pk_col"
4788
+ ]);
4053
4789
  let result = sql;
4054
4790
  for (const [key, rawValue] of Object.entries(params)) {
4055
4791
  const valStr = rawValue !== void 0 && rawValue !== null ? String(rawValue) : "";
4792
+ const isIdentifier = identifierKeys.has(key);
4056
4793
  const isNumber = !isNaN(Number(valStr)) && valStr.trim() !== "";
4057
4794
  const quotedMustache = new RegExp(`'\\{\\{\\s*${key}\\s*\\}\\}'`, "g");
4058
4795
  const quotedColon = new RegExp(`':${key}'`, "g");
@@ -4062,10 +4799,16 @@ function substituteSnippetParams(sql, params, dbType = "sqlite") {
4062
4799
  const rawMustache = new RegExp(`\\{\\{\\s*${key}\\s*\\}\\}`, "g");
4063
4800
  result = result.replace(rawMustache, () => {
4064
4801
  return isNumber ? valStr : valStr;
4802
+ if (isIdentifier) return dialect.quoteIdentifier(valStr);
4803
+ if (isNumber) return valStr;
4804
+ return `'${escapedVal}'`;
4065
4805
  });
4066
4806
  const rawColon = new RegExp(`(^|[^:]):${key}\\b`, "g");
4067
4807
  result = result.replace(rawColon, (_match, prefix) => {
4068
4808
  return `${prefix}${valStr}`;
4809
+ if (isIdentifier) return `${prefix}${dialect.quoteIdentifier(valStr)}`;
4810
+ if (isNumber) return `${prefix}${valStr}`;
4811
+ return `${prefix}'${escapedVal}'`;
4069
4812
  });
4070
4813
  }
4071
4814
  return result;
@@ -4116,6 +4859,96 @@ var init_snippets = __esm({
4116
4859
  }
4117
4860
  });
4118
4861
 
4862
+ // src/logic/version.ts
4863
+ import fs5 from "fs";
4864
+ import path5 from "path";
4865
+ import { fileURLToPath } from "url";
4866
+ function getDrixioVersion() {
4867
+ if (cachedVersion) {
4868
+ return cachedVersion;
4869
+ }
4870
+ if ("1.2.0") {
4871
+ cachedVersion = "1.2.0";
4872
+ return cachedVersion;
4873
+ }
4874
+ try {
4875
+ let currentDir = path5.dirname(fileURLToPath(import.meta.url));
4876
+ for (let i = 0; i < 5; i++) {
4877
+ const candidate = path5.join(currentDir, "package.json");
4878
+ if (fs5.existsSync(candidate)) {
4879
+ const pkg = JSON.parse(fs5.readFileSync(candidate, "utf-8"));
4880
+ if (pkg.name === "drixio" && typeof pkg.version === "string") {
4881
+ cachedVersion = pkg.version;
4882
+ return pkg.version;
4883
+ }
4884
+ }
4885
+ const parent = path5.dirname(currentDir);
4886
+ if (parent === currentDir) break;
4887
+ currentDir = parent;
4888
+ }
4889
+ } catch {
4890
+ }
4891
+ try {
4892
+ const cwdPkgPath = path5.resolve(process.cwd(), "package.json");
4893
+ if (fs5.existsSync(cwdPkgPath)) {
4894
+ const pkg = JSON.parse(fs5.readFileSync(cwdPkgPath, "utf-8"));
4895
+ if (pkg.name === "drixio" && typeof pkg.version === "string") {
4896
+ cachedVersion = pkg.version;
4897
+ return pkg.version;
4898
+ }
4899
+ }
4900
+ } catch {
4901
+ }
4902
+ if (typeof process.env.npm_package_version === "string") {
4903
+ cachedVersion = process.env.npm_package_version;
4904
+ return cachedVersion;
4905
+ }
4906
+ cachedVersion = "0.0.0";
4907
+ return cachedVersion;
4908
+ }
4909
+ var cachedVersion;
4910
+ var init_version = __esm({
4911
+ "src/logic/version.ts"() {
4912
+ "use strict";
4913
+ }
4914
+ });
4915
+
4916
+ // src/logic/serialization.ts
4917
+ var init_serialization = __esm({
4918
+ "src/logic/serialization.ts"() {
4919
+ "use strict";
4920
+ if (!("toJSON" in BigInt.prototype)) {
4921
+ BigInt.prototype.toJSON = function() {
4922
+ return this.toString();
4923
+ };
4924
+ }
4925
+ }
4926
+ });
4927
+
4928
+ // src/logic/repl.ts
4929
+ function parseReplCommand(rawSql) {
4930
+ const trimmed = rawSql.trim();
4931
+ const connectMatch = trimmed.match(/^CONNECT\s+([^\s;]+)\s*;?$/i);
4932
+ if (connectMatch) {
4933
+ return { type: "connect", url: connectMatch[1], originalSql: rawSql };
4934
+ }
4935
+ if (/^DISCONNECT\s*;?$/i.test(trimmed)) {
4936
+ return { type: "disconnect", originalSql: rawSql };
4937
+ }
4938
+ const createDbMatch = trimmed.match(
4939
+ /^CREATE\s+DATABASE\s+(?:IF\s+NOT\s+EXISTS\s+)?['"`]?([^'";`\s]+)['"`]?\s*;?$/i
4940
+ );
4941
+ if (createDbMatch) {
4942
+ return { type: "create_database", dbName: createDbMatch[1], originalSql: rawSql };
4943
+ }
4944
+ return { type: "sql", originalSql: rawSql };
4945
+ }
4946
+ var init_repl = __esm({
4947
+ "src/logic/repl.ts"() {
4948
+ "use strict";
4949
+ }
4950
+ });
4951
+
4119
4952
  // src/logic/index.ts
4120
4953
  var init_logic = __esm({
4121
4954
  "src/logic/index.ts"() {
@@ -4134,6 +4967,9 @@ var init_logic = __esm({
4134
4967
  init_safety();
4135
4968
  init_diff();
4136
4969
  init_snippets();
4970
+ init_version();
4971
+ init_serialization();
4972
+ init_repl();
4137
4973
  }
4138
4974
  });
4139
4975
 
@@ -4177,7 +5013,7 @@ function printCustomDashboard(title, rows) {
4177
5013
  );
4178
5014
  }
4179
5015
  function printDashboard(dbConfig) {
4180
- const version = true ? "1.1.9" : process.env.npm_package_version || "unknown";
5016
+ const version = getDrixioVersion();
4181
5017
  const headerTitle = ` Lightweight Interactive TUI Database Client \u2022 v${version} `;
4182
5018
  let dbTypeVal = "None";
4183
5019
  let targetVal = "-";
@@ -4208,6 +5044,7 @@ var rgb, l1, l2, l3, l4, l5, l6;
4208
5044
  var init_logo = __esm({
4209
5045
  "src/tui/ui/logo.ts"() {
4210
5046
  "use strict";
5047
+ init_version();
4211
5048
  rgb = (r, g, b) => (text) => `\x1B[38;2;${r};${g};${b}m${text}\x1B[39m`;
4212
5049
  l1 = rgb(0, 255, 255);
4213
5050
  l2 = rgb(0, 230, 245);
@@ -4526,8 +5363,8 @@ x Error creating table: ${e.message}`));
4526
5363
  await waitForEnter2();
4527
5364
  }
4528
5365
  async function waitForEnter2() {
4529
- const { input: input8 } = await import("@inquirer/prompts");
4530
- await input8({
5366
+ const { input: input11 } = await import("@inquirer/prompts");
5367
+ await input11({
4531
5368
  message: "Press Enter to continue..."
4532
5369
  });
4533
5370
  }
@@ -4600,8 +5437,8 @@ var init_buildTable = __esm({
4600
5437
  });
4601
5438
 
4602
5439
  // src/tui/views/sqlRunner.ts
4603
- import fs14 from "fs/promises";
4604
- import path14 from "path";
5440
+ import fs15 from "fs/promises";
5441
+ import path15 from "path";
4605
5442
  import { input as input5 } from "@inquirer/prompts";
4606
5443
  import pc29 from "picocolors";
4607
5444
  async function runSqlRunner(dbConfig) {
@@ -4622,10 +5459,10 @@ No database connection found. Please setup first.`)
4622
5459
  message: "Enter the path to your .md or .sql file:"
4623
5460
  });
4624
5461
  if (filePath && filePath.trim()) {
4625
- const absolutePath = path14.resolve(process.cwd(), filePath.trim());
5462
+ const absolutePath = path15.resolve(process.cwd(), filePath.trim());
4626
5463
  let fileContent = "";
4627
5464
  try {
4628
- fileContent = await fs14.readFile(absolutePath, "utf-8");
5465
+ fileContent = await fs15.readFile(absolutePath, "utf-8");
4629
5466
  } catch (err2) {
4630
5467
  console.log(pc29.red(`
4631
5468
  x Failed to read file: ${err2.message}`));
@@ -5157,45 +5994,486 @@ async function handleModifyMenu(dbConfig) {
5157
5994
  await runModifyTable(dbConfig, "delete");
5158
5995
  break;
5159
5996
  }
5160
- await waitForEnter3();
5161
- }
5162
- async function waitForEnter3() {
5163
- const { input: input8 } = await import("@inquirer/prompts");
5164
- await input8({
5165
- message: "Press Enter to continue..."
5166
- });
5997
+ await waitForEnter3();
5998
+ }
5999
+ async function waitForEnter3() {
6000
+ const { input: input11 } = await import("@inquirer/prompts");
6001
+ await input11({
6002
+ message: "Press Enter to continue..."
6003
+ });
6004
+ }
6005
+ var init_tableManagerFlow = __esm({
6006
+ "src/tui/wizards/tableManagerFlow.ts"() {
6007
+ "use strict";
6008
+ init_logic();
6009
+ init_tableManager();
6010
+ init_buildTable();
6011
+ init_sqlRunner();
6012
+ init_modifyTable();
6013
+ init_mockWizard();
6014
+ init_truncateWizard();
6015
+ init_logo();
6016
+ }
6017
+ });
6018
+
6019
+ // src/tui/wizards/ormWizard.ts
6020
+ var ormWizard_exports = {};
6021
+ __export(ormWizard_exports, {
6022
+ runOrmWizard: () => runOrmWizard
6023
+ });
6024
+ import { select as select13, input as input8 } from "@inquirer/prompts";
6025
+ import fs16 from "fs/promises";
6026
+ import path16 from "path";
6027
+ import pc34 from "picocolors";
6028
+ async function runOrmWizard(dbConfig) {
6029
+ if (dbConfig.type === "unknown") {
6030
+ console.log(
6031
+ pc34.yellow(
6032
+ "\nNo database connection found. Please setup database first."
6033
+ )
6034
+ );
6035
+ return;
6036
+ }
6037
+ const adapter = createDBAdapter(dbConfig);
6038
+ try {
6039
+ const allSchemasRes = await getTableSchemas(adapter, void 0, true);
6040
+ if (!allSchemasRes.success) {
6041
+ console.log(
6042
+ pc34.red(`
6043
+ \u2718 Failed to inspect schema: ${allSchemasRes.error}`)
6044
+ );
6045
+ return;
6046
+ }
6047
+ const allTables = allSchemasRes.data;
6048
+ if (allTables.length === 0) {
6049
+ console.log(pc34.yellow("\nNo tables found in this database."));
6050
+ return;
6051
+ }
6052
+ console.log(pc34.cyan("\n--- Modern ORM & Type Definition Generator ---"));
6053
+ console.log(
6054
+ pc34.dim(
6055
+ "Generate production-ready Prisma, Drizzle, or TypeScript definitions.\n"
6056
+ )
6057
+ );
6058
+ const targetFormat = await select13({
6059
+ message: "Select target ORM or definition format:",
6060
+ choices: [
6061
+ {
6062
+ name: "Prisma Schema (schema.prisma)",
6063
+ value: "prisma",
6064
+ description: "Standard Prisma models with data source and generator"
6065
+ },
6066
+ {
6067
+ name: "Drizzle ORM (schema.ts)",
6068
+ value: "drizzle",
6069
+ description: "Type-safe Drizzle table definitions with core packages"
6070
+ },
6071
+ {
6072
+ name: "TypeScript Interfaces (drixio-types.d.ts)",
6073
+ value: "types",
6074
+ description: "Pure TypeScript interface definitions for database records"
6075
+ }
6076
+ ]
6077
+ });
6078
+ const scope = await select13({
6079
+ message: "Select generation scope:",
6080
+ choices: [
6081
+ {
6082
+ name: `All Tables (${allTables.length} tables)`,
6083
+ value: "all"
6084
+ },
6085
+ {
6086
+ name: "Single Table",
6087
+ value: "single"
6088
+ }
6089
+ ]
6090
+ });
6091
+ let selectedTables = allTables;
6092
+ if (scope === "single") {
6093
+ const chosenTableName = await select13({
6094
+ message: "Select table to generate code for:",
6095
+ choices: allTables.map((t) => ({
6096
+ name: t.tableName,
6097
+ value: t.tableName
6098
+ }))
6099
+ });
6100
+ selectedTables = allTables.filter((t) => t.tableName === chosenTableName);
6101
+ }
6102
+ let generatedCode = "";
6103
+ let defaultFileName = "schema.ts";
6104
+ if (targetFormat === "prisma") {
6105
+ generatedCode = generatePrismaSchema(selectedTables, dbConfig.type);
6106
+ defaultFileName = "schema.prisma";
6107
+ } else if (targetFormat === "drizzle") {
6108
+ generatedCode = generateDrizzleSchema(selectedTables, dbConfig.type);
6109
+ defaultFileName = "schema.ts";
6110
+ } else {
6111
+ generatedCode = generateTypeScriptDefinitions(selectedTables, dbConfig.type);
6112
+ defaultFileName = "drixio-types.d.ts";
6113
+ }
6114
+ const outputAction = await select13({
6115
+ message: "What would you like to do with the generated code?",
6116
+ choices: [
6117
+ {
6118
+ name: `Save to file (${defaultFileName})`,
6119
+ value: "save"
6120
+ },
6121
+ {
6122
+ name: "Print directly to terminal",
6123
+ value: "print"
6124
+ }
6125
+ ]
6126
+ });
6127
+ if (outputAction === "print") {
6128
+ console.log(pc34.bold(pc34.cyan(`
6129
+ --- Generated Code ---`)));
6130
+ console.log(generatedCode);
6131
+ console.log(pc34.bold(pc34.cyan(`----------------------
6132
+ `)));
6133
+ } else {
6134
+ const outPath = await input8({
6135
+ message: "Enter destination file path:",
6136
+ default: defaultFileName
6137
+ });
6138
+ const resolvedPath = path16.resolve(process.cwd(), outPath);
6139
+ await fs16.mkdir(path16.dirname(resolvedPath), { recursive: true });
6140
+ await fs16.writeFile(resolvedPath, generatedCode, "utf-8");
6141
+ console.log(
6142
+ pc34.green(
6143
+ `
6144
+ \u2714 Successfully generated and saved to: ${pc34.bold(resolvedPath)}`
6145
+ )
6146
+ );
6147
+ }
6148
+ } catch (e) {
6149
+ console.log(pc34.red(`
6150
+ \u2718 Code generation failed: ${e.message}`));
6151
+ } finally {
6152
+ await adapter.close();
6153
+ }
6154
+ }
6155
+ var init_ormWizard = __esm({
6156
+ "src/tui/wizards/ormWizard.ts"() {
6157
+ "use strict";
6158
+ init_logic();
6159
+ }
6160
+ });
6161
+
6162
+ // src/tui/wizards/snippetsWizard.ts
6163
+ var snippetsWizard_exports = {};
6164
+ __export(snippetsWizard_exports, {
6165
+ runSnippetsWizard: () => runSnippetsWizard
6166
+ });
6167
+ import { select as select14, input as input9 } from "@inquirer/prompts";
6168
+ import pc35 from "picocolors";
6169
+ async function runSnippetsWizard(dbConfig) {
6170
+ if (dbConfig.type === "unknown") {
6171
+ console.log(
6172
+ pc35.yellow(
6173
+ "\nNo database connection found. Please setup database first."
6174
+ )
6175
+ );
6176
+ return;
6177
+ }
6178
+ const snippets = await loadSnippets();
6179
+ if (snippets.length === 0) {
6180
+ console.log(pc35.yellow("\nNo SQL snippets found in workspace."));
6181
+ return;
6182
+ }
6183
+ console.log(pc35.cyan("\n--- SQL Snippets & Operational Templates ---"));
6184
+ console.log(
6185
+ pc35.dim(
6186
+ "Run parameterized diagnostic queries and high-frequency templates.\n"
6187
+ )
6188
+ );
6189
+ const snippetChoices = snippets.map((s) => ({
6190
+ name: `${s.title} ${pc35.dim(`(${s.tags?.join(", ") || "general"})`)}`,
6191
+ value: s.id,
6192
+ description: s.description || s.sql
6193
+ }));
6194
+ const chosenId = await select14({
6195
+ message: "Select a snippet to execute:",
6196
+ choices: snippetChoices
6197
+ });
6198
+ const snippet = snippets.find((s) => s.id === chosenId);
6199
+ if (!snippet) return;
6200
+ console.log(pc35.bold(pc35.cyan(`
6201
+ Template SQL:`)));
6202
+ console.log(pc35.dim(snippet.sql));
6203
+ const params = extractSnippetParams(snippet.sql);
6204
+ const paramValues = {};
6205
+ if (params.length > 0) {
6206
+ console.log(pc35.yellow(`
6207
+ This query requires ${params.length} parameter(s):`));
6208
+ for (const param of params) {
6209
+ let defaultVal = "";
6210
+ if (param === "limit") defaultVal = "20";
6211
+ if (param === "table") defaultVal = "users";
6212
+ const val = await input9({
6213
+ message: `Enter value for :${pc35.bold(param)}:`,
6214
+ default: defaultVal
6215
+ });
6216
+ paramValues[param] = val;
6217
+ }
6218
+ }
6219
+ const finalSql = substituteSnippetParams(
6220
+ snippet.sql,
6221
+ paramValues,
6222
+ dbConfig.type
6223
+ );
6224
+ console.log(pc35.bold(pc35.cyan(`
6225
+ Executing Query:`)));
6226
+ console.log(pc35.white(finalSql));
6227
+ const adapter = createDBAdapter(dbConfig);
6228
+ try {
6229
+ const startTime = performance.now();
6230
+ const result = await adapter.query(finalSql);
6231
+ const elapsed = Math.round(performance.now() - startTime);
6232
+ console.log(
6233
+ pc35.green(
6234
+ `
6235
+ \u2714 Query executed successfully in ${elapsed}ms (${result.rows.length} row(s) returned):
6236
+ `
6237
+ )
6238
+ );
6239
+ if (result.rows.length > 0) {
6240
+ console.table(result.rows.slice(0, 50));
6241
+ if (result.rows.length > 50) {
6242
+ console.log(
6243
+ pc35.dim(`... and ${result.rows.length - 50} more row(s) hidden.`)
6244
+ );
6245
+ }
6246
+ } else {
6247
+ console.log(pc35.dim("(No rows returned)"));
6248
+ }
6249
+ } catch (e) {
6250
+ console.log(pc35.red(`
6251
+ \u2718 Query execution failed: ${e.message}`));
6252
+ } finally {
6253
+ await adapter.close();
6254
+ }
6255
+ }
6256
+ var init_snippetsWizard = __esm({
6257
+ "src/tui/wizards/snippetsWizard.ts"() {
6258
+ "use strict";
6259
+ init_logic();
6260
+ }
6261
+ });
6262
+
6263
+ // src/tui/wizards/diffWizard.ts
6264
+ var diffWizard_exports = {};
6265
+ __export(diffWizard_exports, {
6266
+ runDiffWizard: () => runDiffWizard
6267
+ });
6268
+ import { select as select15, input as input10 } from "@inquirer/prompts";
6269
+ import fs17 from "fs/promises";
6270
+ import { existsSync as existsSync3 } from "fs";
6271
+ import path17 from "path";
6272
+ import pc36 from "picocolors";
6273
+ async function runDiffWizard(dbConfig) {
6274
+ if (dbConfig.type === "unknown") {
6275
+ console.log(
6276
+ pc36.yellow(
6277
+ "\nNo database connection found. Please setup database first."
6278
+ )
6279
+ );
6280
+ return;
6281
+ }
6282
+ console.log(pc36.cyan("\n--- Schema Diff & Migration Wizard ---"));
6283
+ console.log(
6284
+ pc36.dim(
6285
+ "Compare schemas against snapshots or external databases, and generate Up/Down migration SQL.\n"
6286
+ )
6287
+ );
6288
+ const action = await select15({
6289
+ message: "Select diff action:",
6290
+ choices: [
6291
+ {
6292
+ name: "Capture Schema Snapshot (JSON)",
6293
+ value: "snapshot",
6294
+ description: "Save current database structure to a versioned JSON snapshot"
6295
+ },
6296
+ {
6297
+ name: "Compare with Snapshot File (.json)",
6298
+ value: "compare-file",
6299
+ description: "Compare current database with a saved schema snapshot file"
6300
+ },
6301
+ {
6302
+ name: "Compare with Another Database URL",
6303
+ value: "compare-url",
6304
+ description: "Compare current database directly with a remote/local database"
6305
+ }
6306
+ ]
6307
+ });
6308
+ const adapter = createDBAdapter(dbConfig);
6309
+ try {
6310
+ if (action === "snapshot") {
6311
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
6312
+ const defaultFileName = `.drixio/snapshot_${timestamp}.json`;
6313
+ const outPath = await input10({
6314
+ message: "Enter output file path for snapshot:",
6315
+ default: defaultFileName
6316
+ });
6317
+ const resolvedPath = path17.resolve(process.cwd(), outPath);
6318
+ await fs17.mkdir(path17.dirname(resolvedPath), { recursive: true });
6319
+ console.log(pc36.dim("\nCapturing schema snapshot..."));
6320
+ const snapshotRes = await createSchemaSnapshot(
6321
+ adapter,
6322
+ dbConfig.type,
6323
+ path17.basename(outPath, ".json")
6324
+ );
6325
+ if (!snapshotRes.success) {
6326
+ console.log(pc36.red(`
6327
+ \u2718 Failed to create snapshot: ${snapshotRes.error}`));
6328
+ return;
6329
+ }
6330
+ await fs17.writeFile(
6331
+ resolvedPath,
6332
+ JSON.stringify(snapshotRes.data, null, 2),
6333
+ "utf-8"
6334
+ );
6335
+ console.log(
6336
+ pc36.green(
6337
+ `
6338
+ \u2714 Successfully saved snapshot with ${snapshotRes.data.tables.length} table(s) to: ${pc36.bold(resolvedPath)}`
6339
+ )
6340
+ );
6341
+ return;
6342
+ }
6343
+ let diffResult;
6344
+ if (action === "compare-file") {
6345
+ const defaultPath = existsSync3(path17.resolve(process.cwd(), ".drixio/schema.json")) ? ".drixio/schema.json" : "schema.json";
6346
+ const snapshotPath = await input10({
6347
+ message: "Enter path to snapshot JSON file:",
6348
+ default: defaultPath
6349
+ });
6350
+ const resolvedSnapshot = path17.resolve(process.cwd(), snapshotPath);
6351
+ if (!existsSync3(resolvedSnapshot)) {
6352
+ console.log(pc36.red(`
6353
+ \u2718 Snapshot file not found: ${resolvedSnapshot}`));
6354
+ return;
6355
+ }
6356
+ console.log(pc36.dim("\nComparing current database with snapshot..."));
6357
+ const content = await fs17.readFile(resolvedSnapshot, "utf-8");
6358
+ let snapshot;
6359
+ try {
6360
+ snapshot = JSON.parse(content);
6361
+ } catch {
6362
+ console.log(pc36.red(`
6363
+ \u2718 Invalid JSON content in snapshot file: ${resolvedSnapshot}`));
6364
+ return;
6365
+ }
6366
+ const res = await diffDatabaseWithSnapshot(
6367
+ adapter,
6368
+ snapshot,
6369
+ dbConfig.type,
6370
+ "Current Database",
6371
+ `Snapshot (${path17.basename(resolvedSnapshot)})`
6372
+ );
6373
+ if (!res.success) {
6374
+ console.log(pc36.red(`
6375
+ \u2718 Diff failed: ${res.error}`));
6376
+ return;
6377
+ }
6378
+ diffResult = res.data;
6379
+ } else {
6380
+ const targetUrl = await input10({
6381
+ message: "Enter target database connection URL (e.g. postgres://user:pass@host:5432/other_db):"
6382
+ });
6383
+ if (!targetUrl.trim()) {
6384
+ console.log(pc36.yellow("Target URL cannot be empty."));
6385
+ return;
6386
+ }
6387
+ console.log(pc36.dim("\nConnecting to target database and comparing..."));
6388
+ const targetConfig = await detectDatabase(targetUrl.trim());
6389
+ const targetAdapter = createDBAdapter(targetConfig);
6390
+ try {
6391
+ const res = await diffDatabases(
6392
+ adapter,
6393
+ targetAdapter,
6394
+ dbConfig.type,
6395
+ "Current Database",
6396
+ "Target Database"
6397
+ );
6398
+ if (!res.success) {
6399
+ console.log(pc36.red(`
6400
+ \u2718 Diff failed: ${res.error}`));
6401
+ return;
6402
+ }
6403
+ diffResult = res.data;
6404
+ } finally {
6405
+ await targetAdapter.close();
6406
+ }
6407
+ }
6408
+ if (!diffResult.hasChanges) {
6409
+ console.log(pc36.green("\n\u2714 No schema differences detected. Both schemas are identical!"));
6410
+ return;
6411
+ }
6412
+ console.log(pc36.bold(pc36.yellow(`
6413
+ --- Schema Differences Detected ---`)));
6414
+ console.log(` Added Tables: ${diffResult.stats.addedTablesCount}`);
6415
+ console.log(` Dropped Tables: ${diffResult.stats.droppedTablesCount}`);
6416
+ console.log(` Altered Tables: ${diffResult.stats.alteredTablesCount}`);
6417
+ console.log(` Added Columns: ${diffResult.stats.addedColumnsCount}`);
6418
+ console.log(` Dropped Columns: ${diffResult.stats.droppedColumnsCount}`);
6419
+ console.log(` Added Indexes: ${diffResult.stats.addedIndexesCount}`);
6420
+ console.log(` Dropped Indexes: ${diffResult.stats.droppedIndexesCount}`);
6421
+ const nextStep = await select15({
6422
+ message: "What would you like to inspect?",
6423
+ choices: [
6424
+ {
6425
+ name: "View Forward Migration SQL (Up)",
6426
+ value: "view-up"
6427
+ },
6428
+ {
6429
+ name: "View Rollback SQL (Down)",
6430
+ value: "view-down"
6431
+ },
6432
+ {
6433
+ name: "Save Migration SQL to File",
6434
+ value: "save-sql"
6435
+ }
6436
+ ]
6437
+ });
6438
+ if (nextStep === "view-up") {
6439
+ console.log(pc36.bold(pc36.cyan("\n--- Forward Migration SQL (Up) ---")));
6440
+ console.log(diffResult.migrationSql);
6441
+ console.log(pc36.bold(pc36.cyan("----------------------------------\n")));
6442
+ } else if (nextStep === "view-down") {
6443
+ console.log(pc36.bold(pc36.cyan("\n--- Rollback SQL (Down) ---")));
6444
+ console.log(diffResult.rollbackSql);
6445
+ console.log(pc36.bold(pc36.cyan("---------------------------\n")));
6446
+ } else if (nextStep === "save-sql") {
6447
+ const outSqlPath = await input10({
6448
+ message: "Enter destination file path:",
6449
+ default: "migration.sql"
6450
+ });
6451
+ const resolvedOut = path17.resolve(process.cwd(), outSqlPath);
6452
+ await fs17.mkdir(path17.dirname(resolvedOut), { recursive: true });
6453
+ await fs17.writeFile(resolvedOut, diffResult.migrationSql, "utf-8");
6454
+ console.log(
6455
+ pc36.green(`
6456
+ \u2714 Migration SQL successfully saved to: ${pc36.bold(resolvedOut)}`)
6457
+ );
6458
+ }
6459
+ } catch (e) {
6460
+ console.log(pc36.red(`
6461
+ \u2718 Schema diff failed: ${e.message}`));
6462
+ } finally {
6463
+ await adapter.close();
6464
+ }
5167
6465
  }
5168
- var init_tableManagerFlow = __esm({
5169
- "src/tui/wizards/tableManagerFlow.ts"() {
6466
+ var init_diffWizard = __esm({
6467
+ "src/tui/wizards/diffWizard.ts"() {
5170
6468
  "use strict";
5171
6469
  init_logic();
5172
- init_tableManager();
5173
- init_buildTable();
5174
- init_sqlRunner();
5175
- init_modifyTable();
5176
- init_mockWizard();
5177
- init_truncateWizard();
5178
- init_logo();
5179
6470
  }
5180
6471
  });
5181
6472
 
5182
6473
  // src/studio/api.ts
5183
6474
  import { Hono } from "hono";
5184
- import { spawn } from "child_process";
5185
- import path15 from "path";
5186
- import fs15 from "fs";
5187
- function nodeToWebStream(nodeStream) {
5188
- return new ReadableStream({
5189
- start(controller) {
5190
- nodeStream.on("data", (chunk) => controller.enqueue(chunk));
5191
- nodeStream.on("end", () => controller.close());
5192
- nodeStream.on("error", (err2) => controller.error(err2));
5193
- },
5194
- cancel() {
5195
- nodeStream.destroy();
5196
- }
5197
- });
5198
- }
6475
+ import path18 from "path";
6476
+ import fs18 from "fs";
5199
6477
  function registerApiRoutes(app, dbConfig) {
5200
6478
  const api = new Hono();
5201
6479
  let currentDbConfig = { ...dbConfig };
@@ -5279,15 +6557,41 @@ function registerApiRoutes(app, dbConfig) {
5279
6557
  return c.json({ success: false, error: e.message }, 500);
5280
6558
  }
5281
6559
  });
6560
+ const detectHostEnvironment = (cfg) => {
6561
+ if (cfg.type === "sqlite") {
6562
+ return {
6563
+ isRemote: false,
6564
+ host: "local",
6565
+ badgeLabel: "LOCAL (SQLite)"
6566
+ };
6567
+ }
6568
+ if (cfg.type === "mysql" || cfg.type === "postgres") {
6569
+ let host = "localhost";
6570
+ const match = cfg.targetUrl.match(/@([^:/@?]+)/);
6571
+ if (match) host = match[1];
6572
+ const isLocal = host === "localhost" || host === "127.0.0.1" || host === "::1" || host === "0.0.0.0" || host === "host.docker.internal";
6573
+ return {
6574
+ isRemote: !isLocal,
6575
+ host,
6576
+ badgeLabel: isLocal ? `LOCAL (${cfg.type.toUpperCase()})` : `REMOTE (${host})`
6577
+ };
6578
+ }
6579
+ return { isRemote: false, host: "", badgeLabel: "DISCONNECTED" };
6580
+ };
5282
6581
  api.get("/config", (c) => {
5283
6582
  const isConnected = !!currentAdapter && currentDbConfig.type !== "unknown";
6583
+ const envInfo = detectHostEnvironment(currentDbConfig);
5284
6584
  return c.json({
5285
6585
  success: true,
5286
6586
  data: {
6587
+ appVersion: getDrixioVersion(),
5287
6588
  connected: isConnected,
5288
6589
  dbType: isConnected ? currentDbConfig.type : "none",
5289
6590
  dbName: isConnected ? getDbName() : "No Database",
5290
- targetUrl: currentDbConfig.targetUrl || ""
6591
+ targetUrl: currentDbConfig.targetUrl || "",
6592
+ isRemote: envInfo.isRemote,
6593
+ host: envInfo.host,
6594
+ badgeLabel: isConnected ? envInfo.badgeLabel : "DISCONNECTED"
5291
6595
  }
5292
6596
  });
5293
6597
  });
@@ -5538,7 +6842,7 @@ function registerApiRoutes(app, dbConfig) {
5538
6842
  return c.json({ success: false, error: e.message }, 500);
5539
6843
  }
5540
6844
  });
5541
- api.post("/analyze-query", async (c) => {
6845
+ api.post("/query/analyze", async (c) => {
5542
6846
  try {
5543
6847
  const { sql } = await c.req.json().catch(() => ({}));
5544
6848
  const result = analyzeDangerousQuery(sql);
@@ -5556,10 +6860,9 @@ function registerApiRoutes(app, dbConfig) {
5556
6860
  400
5557
6861
  );
5558
6862
  }
5559
- const trimmed = sql.trim();
5560
- const connectMatch = trimmed.match(/^CONNECT\s+([^\s;]+)\s*;?$/i);
5561
- if (connectMatch) {
5562
- const rawUrl = connectMatch[1];
6863
+ const replCmd = parseReplCommand(sql);
6864
+ if (replCmd.type === "connect" && replCmd.url) {
6865
+ const rawUrl = replCmd.url;
5563
6866
  const newConfig = await detectDatabase(rawUrl);
5564
6867
  const newAdapter = createDBAdapter(newConfig);
5565
6868
  await newAdapter.getTables();
@@ -5591,7 +6894,7 @@ function registerApiRoutes(app, dbConfig) {
5591
6894
  }
5592
6895
  });
5593
6896
  }
5594
- if (/^DISCONNECT\s*;?$/i.test(trimmed)) {
6897
+ if (replCmd.type === "disconnect") {
5595
6898
  if (currentAdapter) {
5596
6899
  try {
5597
6900
  await currentAdapter.close();
@@ -5618,11 +6921,8 @@ function registerApiRoutes(app, dbConfig) {
5618
6921
  }
5619
6922
  });
5620
6923
  }
5621
- const createDbMatch = trimmed.match(
5622
- /^CREATE\s+DATABASE\s+(?:IF\s+NOT\s+EXISTS\s+)?['"`]?([^'";`\s]+)['"`]?\s*;?$/i
5623
- );
5624
- if (createDbMatch) {
5625
- const dbName = createDbMatch[1];
6924
+ if (replCmd.type === "create_database" && replCmd.dbName) {
6925
+ const dbName = replCmd.dbName;
5626
6926
  if (currentAdapter && (currentDbConfig.type === "postgres" || currentDbConfig.type === "mysql")) {
5627
6927
  const quote = currentDbConfig.type === "mysql" ? "`" : '"';
5628
6928
  await currentAdapter.executeSql(
@@ -5694,6 +6994,49 @@ function registerApiRoutes(app, dbConfig) {
5694
6994
  return c.json({ success: false, error: e.message }, 500);
5695
6995
  }
5696
6996
  });
6997
+ api.post("/query/explain", async (c) => {
6998
+ try {
6999
+ const { sql } = await c.req.json().catch(() => ({}));
7000
+ if (!sql || typeof sql !== "string") {
7001
+ return c.json(
7002
+ { success: false, error: "SQL query is required for explain" },
7003
+ 400
7004
+ );
7005
+ }
7006
+ const adapter = getAdapter();
7007
+ const explainRes = generateExplainQuery(sql, currentDbConfig.type);
7008
+ if (explainRes.error || !explainRes.sql) {
7009
+ return c.json({ success: false, error: explainRes.error }, 400);
7010
+ }
7011
+ let explainSql = explainRes.sql;
7012
+ const startTime = performance.now();
7013
+ let res;
7014
+ try {
7015
+ res = await adapter.query(explainSql);
7016
+ } catch (primaryErr) {
7017
+ if (currentDbConfig.type === "postgres") {
7018
+ const fallbackRes = generateExplainQuery(sql, "unknown");
7019
+ explainSql = fallbackRes.sql;
7020
+ res = await adapter.query(explainSql);
7021
+ } else {
7022
+ throw primaryErr;
7023
+ }
7024
+ }
7025
+ const durationMs = Math.round(performance.now() - startTime);
7026
+ return c.json({
7027
+ success: true,
7028
+ data: {
7029
+ columns: res.columns,
7030
+ rows: res.rows,
7031
+ durationMs,
7032
+ explainSql,
7033
+ dialect: currentDbConfig.type
7034
+ }
7035
+ });
7036
+ } catch (e) {
7037
+ return c.json({ success: false, error: e.message }, 500);
7038
+ }
7039
+ });
5697
7040
  api.post("/query/export", async (c) => {
5698
7041
  try {
5699
7042
  const body = await c.req.json();
@@ -5766,68 +7109,25 @@ function registerApiRoutes(app, dbConfig) {
5766
7109
  }
5767
7110
  const type = currentDbConfig.type;
5768
7111
  const url = currentDbConfig.targetUrl;
5769
- let child;
5770
7112
  let filename = "backup.sql";
5771
- const executeNativeDump = (cmd, args, envName) => {
5772
- return new Promise((resolve, reject) => {
5773
- const cp = spawn(cmd, args);
5774
- let started = false;
5775
- const stream = nodeToWebStream(cp.stdout);
5776
- cp.on("error", (err2) => {
5777
- if (!started)
5778
- reject(
5779
- new Error(
5780
- `Native tool '${cmd}' not found. Please install ${envName}.`
5781
- )
5782
- );
5783
- });
5784
- setTimeout(() => {
5785
- if (!cp.killed) {
5786
- started = true;
5787
- resolve(stream);
5788
- }
5789
- }, 100);
5790
- });
5791
- };
5792
7113
  try {
5793
- let stream;
5794
7114
  const baseName = getDbName();
5795
7115
  const timeStr = getDatetimeStr();
5796
7116
  filename = `${baseName}_backup_${timeStr}.sql`;
5797
- if (type === "sqlite") {
5798
- const dbPath = url.replace("file:", "");
5799
- stream = await executeNativeDump(
5800
- "sqlite3",
5801
- [dbPath, ".dump"],
5802
- "SQLite CLI"
5803
- );
5804
- } else if (type === "mysql") {
5805
- const parsed = new URL(url);
5806
- const user = parsed.username;
5807
- const pass = parsed.password;
5808
- const host = parsed.hostname;
5809
- const port = parsed.port || "3306";
5810
- const dbname = parsed.pathname.substring(1);
5811
- stream = await executeNativeDump(
5812
- "mysqldump",
5813
- ["-u", user, `-p${pass}`, "-h", host, "-P", port, dbname],
5814
- "MySQL Client"
5815
- );
5816
- } else if (type === "postgres") {
5817
- stream = await executeNativeDump(
5818
- "pg_dump",
5819
- [url],
5820
- "PostgreSQL CLI"
5821
- );
5822
- } else {
5823
- throw new Error("Unsupported database type for native export");
7117
+ const exportRes = await exportDatabaseNative(
7118
+ type,
7119
+ url,
7120
+ false
7121
+ );
7122
+ if (!exportRes.success) {
7123
+ throw new Error(exportRes.error);
5824
7124
  }
5825
7125
  c.header(
5826
7126
  "Content-Disposition",
5827
7127
  `attachment; filename="${filename}"`
5828
7128
  );
5829
7129
  c.header("Content-Type", "application/sql");
5830
- return c.body(stream);
7130
+ return c.body(exportRes.data);
5831
7131
  } catch (err2) {
5832
7132
  if (err2.message.includes("Native tool")) {
5833
7133
  const baseName = getDbName();
@@ -5883,67 +7183,14 @@ function registerApiRoutes(app, dbConfig) {
5883
7183
  const sqlContent = await file.text();
5884
7184
  const type = currentDbConfig.type;
5885
7185
  const url = currentDbConfig.targetUrl;
5886
- const executeNativeImport = (cmd, args, fileContent, envName) => {
5887
- return new Promise((resolve, reject) => {
5888
- const cp = spawn(cmd, args);
5889
- let started = false;
5890
- let errStr = "";
5891
- cp.on("error", (err2) => {
5892
- if (!started)
5893
- reject(
5894
- new Error(
5895
- `Native tool '${cmd}' not found. Please install ${envName}.`
5896
- )
5897
- );
5898
- });
5899
- cp.stderr.on("data", (d) => errStr += d.toString());
5900
- cp.on("close", (code) => {
5901
- if (code === 0) resolve();
5902
- else reject(new Error(`Native import failed: ${errStr}`));
5903
- });
5904
- cp.stdin.write(fileContent);
5905
- cp.stdin.end();
5906
- started = true;
5907
- });
5908
- };
5909
- try {
5910
- if (type === "sqlite") {
5911
- const dbPath = url.replace("file:", "");
5912
- await executeNativeImport(
5913
- "sqlite3",
5914
- [dbPath],
5915
- sqlContent,
5916
- "SQLite CLI"
5917
- );
5918
- } else if (type === "mysql") {
5919
- const parsed = new URL(url);
5920
- const user = parsed.username;
5921
- const pass = parsed.password;
5922
- const host = parsed.hostname;
5923
- const port = parsed.port || "3306";
5924
- const dbname = parsed.pathname.substring(1);
5925
- await executeNativeImport(
5926
- "mysql",
5927
- ["-u", user, `-p${pass}`, "-h", host, "-P", port, dbname],
5928
- sqlContent,
5929
- "MySQL Client"
5930
- );
5931
- } else if (type === "postgres") {
5932
- await executeNativeImport(
5933
- "psql",
5934
- [url],
5935
- sqlContent,
5936
- "PostgreSQL CLI"
5937
- );
5938
- } else {
5939
- throw new Error("Unsupported database type for native import");
5940
- }
5941
- } catch (err2) {
5942
- if (err2.message.includes("Native tool")) {
5943
- await getAdapter().executeSql(sqlContent);
5944
- } else {
5945
- throw err2;
5946
- }
7186
+ const importRes = await importDatabaseNative(
7187
+ getAdapter(),
7188
+ type,
7189
+ url,
7190
+ sqlContent
7191
+ );
7192
+ if (!importRes.success) {
7193
+ return c.json({ success: false, error: importRes.error }, 500);
5947
7194
  }
5948
7195
  return c.json({
5949
7196
  success: true,
@@ -6283,11 +7530,10 @@ function registerApiRoutes(app, dbConfig) {
6283
7530
  const dialect = (dbType || "sqlite").toLowerCase();
6284
7531
  if (dialect === "sqlite") {
6285
7532
  const rawPath = body.url || body.sqlitePath || dbName || "drixio.sqlite";
6286
- const cleanPath = rawPath.replace(/^file:/, "").trim();
6287
- const fullPath = path15.isAbsolute(cleanPath) ? cleanPath : path15.resolve(process.cwd(), cleanPath);
7533
+ const fullPath = resolveLocalDbPath(rawPath, process.cwd());
6288
7534
  if (mode === "create") {
6289
- const dir = path15.dirname(fullPath);
6290
- if (!fs15.existsSync(dir)) {
7535
+ const dir = path18.dirname(fullPath);
7536
+ if (!fs18.existsSync(dir)) {
6291
7537
  return c.json(
6292
7538
  {
6293
7539
  success: false,
@@ -6296,18 +7542,18 @@ function registerApiRoutes(app, dbConfig) {
6296
7542
  400
6297
7543
  );
6298
7544
  }
6299
- const exists = fs15.existsSync(fullPath);
7545
+ const exists = fs18.existsSync(fullPath);
6300
7546
  const latencyMs2 = Date.now() - startTime;
6301
7547
  return c.json({
6302
7548
  success: true,
6303
7549
  data: {
6304
7550
  latencyMs: latencyMs2,
6305
- message: exists ? `File already exists at "${path15.basename(fullPath)}". Will connect and reuse.` : `Target path is valid. File will be created on connect.`,
7551
+ message: exists ? `File already exists at "${path18.basename(fullPath)}". Will connect and reuse.` : `Target path is valid. File will be created on connect.`,
6306
7552
  fullPath
6307
7553
  }
6308
7554
  });
6309
7555
  } else {
6310
- if (!fs15.existsSync(fullPath)) {
7556
+ if (!fs18.existsSync(fullPath)) {
6311
7557
  return c.json(
6312
7558
  {
6313
7559
  success: false,
@@ -6316,7 +7562,7 @@ function registerApiRoutes(app, dbConfig) {
6316
7562
  400
6317
7563
  );
6318
7564
  }
6319
- const stats = fs15.statSync(fullPath);
7565
+ const stats = fs18.statSync(fullPath);
6320
7566
  if (stats.isDirectory()) {
6321
7567
  return c.json(
6322
7568
  {
@@ -6344,12 +7590,7 @@ function registerApiRoutes(app, dbConfig) {
6344
7590
  }
6345
7591
  let targetUrl = "";
6346
7592
  if (mode === "create") {
6347
- const targetHost = host || "localhost";
6348
- const targetPort = port || (dialect === "postgres" ? "5432" : "3306");
6349
- const targetUser = user || (dialect === "postgres" ? "postgres" : "root");
6350
- const auth = password ? `${encodeURIComponent(targetUser)}:${encodeURIComponent(password)}` : encodeURIComponent(targetUser);
6351
- const defaultDb = dialect === "postgres" ? "postgres" : "";
6352
- targetUrl = `${dialect}://${auth}@${targetHost}:${targetPort}/${defaultDb}`;
7593
+ targetUrl = assembleConnectionUrl(dialect, host, port, user, password, "");
6353
7594
  } else {
6354
7595
  const rawUrl = body.url;
6355
7596
  if (!rawUrl) {
@@ -6417,6 +7658,73 @@ function registerApiRoutes(app, dbConfig) {
6417
7658
  return c.json({ success: false, error: e.message }, 500);
6418
7659
  }
6419
7660
  });
7661
+ api.get("/schemas", async (c) => {
7662
+ try {
7663
+ const adapter = currentAdapter;
7664
+ const supported = !!adapter && typeof adapter.getSchemas === "function" && currentDbConfig.type === "postgres";
7665
+ if (!supported || !adapter) {
7666
+ return c.json({
7667
+ success: true,
7668
+ data: { supported: false, schemas: [], currentSchema: "public" }
7669
+ });
7670
+ }
7671
+ const schemas = await adapter.getSchemas();
7672
+ const currentSchema = adapter.getCurrentSchema ? adapter.getCurrentSchema() : "public";
7673
+ return c.json({
7674
+ success: true,
7675
+ data: { supported: true, schemas, currentSchema }
7676
+ });
7677
+ } catch (e) {
7678
+ return c.json({ success: false, error: e.message }, 500);
7679
+ }
7680
+ });
7681
+ api.post("/schemas/switch", async (c) => {
7682
+ try {
7683
+ const { schema } = await c.req.json().catch(() => ({}));
7684
+ if (!schema || typeof schema !== "string") {
7685
+ return c.json(
7686
+ { success: false, error: "Schema name is required" },
7687
+ 400
7688
+ );
7689
+ }
7690
+ const adapter = currentAdapter;
7691
+ if (!adapter || typeof adapter.setSchema !== "function" || currentDbConfig.type !== "postgres") {
7692
+ return c.json(
7693
+ {
7694
+ success: false,
7695
+ error: "Schema switching is only supported for PostgreSQL"
7696
+ },
7697
+ 400
7698
+ );
7699
+ }
7700
+ await adapter.setSchema(schema);
7701
+ const tables = await adapter.getTables();
7702
+ return c.json({
7703
+ success: true,
7704
+ data: {
7705
+ schema,
7706
+ tableCount: tables.length
7707
+ }
7708
+ });
7709
+ } catch (e) {
7710
+ return c.json({ success: false, error: e.message }, 500);
7711
+ }
7712
+ });
7713
+ api.get("/database/enums", async (c) => {
7714
+ try {
7715
+ const adapter = currentAdapter;
7716
+ if (!adapter) {
7717
+ return c.json({ success: true, data: [] });
7718
+ }
7719
+ const result = await getDatabaseEnums(adapter);
7720
+ if (!result.success) {
7721
+ return c.json({ success: false, error: result.error }, 500);
7722
+ }
7723
+ return c.json({ success: true, data: result.data });
7724
+ } catch (e) {
7725
+ return c.json({ success: false, error: e.message }, 500);
7726
+ }
7727
+ });
6420
7728
  app.route("/api", api);
6421
7729
  }
6422
7730
  var init_api = __esm({
@@ -6435,27 +7743,27 @@ import { serve } from "@hono/node-server";
6435
7743
  import { Hono as Hono2 } from "hono";
6436
7744
  import { serveStatic } from "@hono/node-server/serve-static";
6437
7745
  import open from "open";
6438
- import path16 from "path";
6439
- import pc35 from "picocolors";
6440
- import { fileURLToPath } from "url";
7746
+ import path19 from "path";
7747
+ import pc38 from "picocolors";
7748
+ import { fileURLToPath as fileURLToPath2 } from "url";
6441
7749
  async function runStudio(dbConfig) {
6442
7750
  const app = new Hono2();
6443
7751
  registerApiRoutes(app, dbConfig);
6444
7752
  const isDev = __dirname.includes("src") || __dirname.includes("studio");
6445
- const studioDistPath = isDev ? path16.resolve(__dirname, "../../dist/studio") : path16.resolve(__dirname, "./studio");
6446
- const fs16 = await import("fs/promises");
7753
+ const studioDistPath = isDev ? path19.resolve(__dirname, "../../dist/studio") : path19.resolve(__dirname, "./studio");
7754
+ const fs19 = await import("fs/promises");
6447
7755
  app.use("/*", async (c, next) => {
6448
7756
  c.header("Cache-Control", "no-cache, no-store, must-revalidate");
6449
7757
  await next();
6450
7758
  });
6451
7759
  app.use(
6452
7760
  "/*",
6453
- serveStatic({ root: path16.relative(process.cwd(), studioDistPath) })
7761
+ serveStatic({ root: path19.relative(process.cwd(), studioDistPath) })
6454
7762
  );
6455
7763
  app.get("*", async (c) => {
6456
- const indexPath = path16.join(studioDistPath, "index.html");
7764
+ const indexPath = path19.join(studioDistPath, "index.html");
6457
7765
  try {
6458
- const html = await fs16.readFile(indexPath, "utf-8");
7766
+ const html = await fs19.readFile(indexPath, "utf-8");
6459
7767
  return c.html(html);
6460
7768
  } catch (e) {
6461
7769
  return c.text(
@@ -6467,23 +7775,23 @@ async function runStudio(dbConfig) {
6467
7775
  const defaultPort = process.env.PORT ? parseInt(process.env.PORT, 10) : 51213;
6468
7776
  const port = await getAvailablePort(defaultPort);
6469
7777
  console.log(
6470
- pc35.cyan(`
7778
+ pc38.cyan(`
6471
7779
  Starting Drixio Studio on http://localhost:${port}...`)
6472
7780
  );
6473
7781
  if (dbConfig.type === "unknown") {
6474
7782
  console.log(
6475
- pc35.yellow(
7783
+ pc38.yellow(
6476
7784
  `\u26A0\uFE0F No database detected. Studio started in standalone mode.`
6477
7785
  )
6478
7786
  );
6479
7787
  console.log(
6480
- pc35.dim(
7788
+ pc38.dim(
6481
7789
  `You can create or connect to a database in the SQL Console.
6482
7790
  `
6483
7791
  )
6484
7792
  );
6485
7793
  }
6486
- console.log(pc35.dim(`Press Ctrl+C to stop the server.
7794
+ console.log(pc38.dim(`Press Ctrl+C to stop the server.
6487
7795
  `));
6488
7796
  serve({
6489
7797
  fetch: app.fetch,
@@ -6512,14 +7820,14 @@ var init_studio = __esm({
6512
7820
  "src/studio/index.ts"() {
6513
7821
  "use strict";
6514
7822
  init_api();
6515
- __filename = fileURLToPath(import.meta.url);
6516
- __dirname = path16.dirname(__filename);
7823
+ __filename = fileURLToPath2(import.meta.url);
7824
+ __dirname = path19.dirname(__filename);
6517
7825
  }
6518
7826
  });
6519
7827
 
6520
7828
  // bin/cli.ts
6521
7829
  init_logic();
6522
- import pc36 from "picocolors";
7830
+ import pc39 from "picocolors";
6523
7831
  import { parseArgs } from "util";
6524
7832
 
6525
7833
  // src/commands/query.ts
@@ -6603,8 +7911,8 @@ async function runQueryCommand(dbConfig, args) {
6603
7911
  const adapter = createDBAdapter(dbConfig);
6604
7912
  let sql = args[0];
6605
7913
  if (!sql) {
6606
- const { input: input8 } = await import("@inquirer/prompts");
6607
- sql = await input8({
7914
+ const { input: input11 } = await import("@inquirer/prompts");
7915
+ sql = await input11({
6608
7916
  message: "Enter your SQL query:"
6609
7917
  });
6610
7918
  }
@@ -6637,8 +7945,8 @@ Query Error: ${e.message}
6637
7945
  // src/commands/export.ts
6638
7946
  init_logic();
6639
7947
  import pc3 from "picocolors";
6640
- import fs5 from "fs/promises";
6641
- import path5 from "path";
7948
+ import fs6 from "fs/promises";
7949
+ import path6 from "path";
6642
7950
  async function runExportCommand(dbConfig, args, options) {
6643
7951
  if (dbConfig.type === "unknown") {
6644
7952
  console.log(
@@ -6650,7 +7958,7 @@ async function runExportCommand(dbConfig, args, options) {
6650
7958
  let tableName = args[0];
6651
7959
  let format = options.format?.toLowerCase();
6652
7960
  const schemaOnly = options["schema-only"];
6653
- const { select: select13 } = await import("@inquirer/prompts");
7961
+ const { select: select16 } = await import("@inquirer/prompts");
6654
7962
  if (!tableName) {
6655
7963
  const allTables = await adapter.getTables();
6656
7964
  if (allTables.length === 0) {
@@ -6661,13 +7969,13 @@ async function runExportCommand(dbConfig, args, options) {
6661
7969
  { name: "All Tables (*)", value: "*" },
6662
7970
  ...allTables.map((t) => ({ name: t, value: t }))
6663
7971
  ];
6664
- tableName = await select13({
7972
+ tableName = await select16({
6665
7973
  message: "Which table do you want to export?",
6666
7974
  choices: tableChoices
6667
7975
  });
6668
7976
  }
6669
7977
  if (!format || !["csv", "json"].includes(format)) {
6670
- format = await select13({
7978
+ format = await select16({
6671
7979
  message: "Which format do you want to export?",
6672
7980
  choices: [
6673
7981
  { name: "CSV", value: "csv" },
@@ -6675,8 +7983,8 @@ async function runExportCommand(dbConfig, args, options) {
6675
7983
  ]
6676
7984
  });
6677
7985
  }
6678
- const exportDir = path5.join(process.cwd(), "drixio_exports");
6679
- await fs5.mkdir(exportDir, { recursive: true });
7986
+ const exportDir = path6.join(process.cwd(), "drixio_exports");
7987
+ await fs6.mkdir(exportDir, { recursive: true });
6680
7988
  const tablesToExport = tableName === "*" ? await adapter.getTables() : [tableName];
6681
7989
  console.log(pc3.cyan(`
6682
7990
  Starting export to ${exportDir}...`));
@@ -6695,8 +8003,8 @@ Starting export to ${exportDir}...`));
6695
8003
  const content = exportRes.data;
6696
8004
  const suffix = schemaOnly ? "_schema" : "_data";
6697
8005
  const ext = format === "json" ? ".json" : ".csv";
6698
- const fp = path5.join(exportDir, `${table}${suffix}${ext}`);
6699
- await fs5.writeFile(fp, content, "utf-8");
8006
+ const fp = path6.join(exportDir, `${table}${suffix}${ext}`);
8007
+ await fs6.writeFile(fp, content, "utf-8");
6700
8008
  const typeLabel = schemaOnly ? "Schema" : "Data";
6701
8009
  console.log(
6702
8010
  pc3.green(
@@ -6715,8 +8023,8 @@ Starting export to ${exportDir}...`));
6715
8023
  // src/commands/import.ts
6716
8024
  init_logic();
6717
8025
  import pc4 from "picocolors";
6718
- import fs6 from "fs/promises";
6719
- import path6 from "path";
8026
+ import fs7 from "fs/promises";
8027
+ import path7 from "path";
6720
8028
  async function runImportCommand(dbConfig, args, options) {
6721
8029
  if (dbConfig.type === "unknown") {
6722
8030
  console.log(
@@ -6727,15 +8035,15 @@ async function runImportCommand(dbConfig, args, options) {
6727
8035
  const adapter = createDBAdapter(dbConfig);
6728
8036
  let filePath = args[0];
6729
8037
  let tableName = options.table;
6730
- const { input: input8, select: select13 } = await import("@inquirer/prompts");
8038
+ const { input: input11, select: select16 } = await import("@inquirer/prompts");
6731
8039
  if (!filePath) {
6732
- filePath = await input8({
8040
+ filePath = await input11({
6733
8041
  message: "Enter the path to your CSV or JSON file:"
6734
8042
  });
6735
8043
  }
6736
- const resolvedPath = path6.resolve(process.cwd(), filePath);
8044
+ const resolvedPath = path7.resolve(process.cwd(), filePath);
6737
8045
  try {
6738
- await fs6.access(resolvedPath);
8046
+ await fs7.access(resolvedPath);
6739
8047
  } catch {
6740
8048
  console.log(pc4.red(`Error: File not found at ${resolvedPath}`));
6741
8049
  process.exit(1);
@@ -6748,14 +8056,14 @@ async function runImportCommand(dbConfig, args, options) {
6748
8056
  );
6749
8057
  process.exit(1);
6750
8058
  }
6751
- tableName = await select13({
8059
+ tableName = await select16({
6752
8060
  message: "Which table do you want to import data into?",
6753
8061
  choices: allTables.map((t) => ({ name: t, value: t }))
6754
8062
  });
6755
8063
  }
6756
8064
  console.log(pc4.cyan(`
6757
8065
  Reading file...`));
6758
- const fileContent = await fs6.readFile(resolvedPath, "utf-8");
8066
+ const fileContent = await fs7.readFile(resolvedPath, "utf-8");
6759
8067
  const isJson = filePath.toLowerCase().endsWith(".json");
6760
8068
  const isCsv = filePath.toLowerCase().endsWith(".csv");
6761
8069
  if (!isJson && !isCsv) {
@@ -6808,7 +8116,7 @@ async function runSeedCommand(dbConfig, args) {
6808
8116
  const adapter = createDBAdapter(dbConfig);
6809
8117
  let tableName = args[0];
6810
8118
  let countStr = args[1];
6811
- const { input: input8, select: select13 } = await import("@inquirer/prompts");
8119
+ const { input: input11, select: select16 } = await import("@inquirer/prompts");
6812
8120
  if (!tableName) {
6813
8121
  const allTables = await adapter.getTables();
6814
8122
  if (allTables.length === 0) {
@@ -6817,14 +8125,14 @@ async function runSeedCommand(dbConfig, args) {
6817
8125
  );
6818
8126
  process.exit(1);
6819
8127
  }
6820
- tableName = await select13({
8128
+ tableName = await select16({
6821
8129
  message: "Which table do you want to seed with realistic fake data?",
6822
8130
  choices: allTables.map((t) => ({ name: t, value: t }))
6823
8131
  });
6824
8132
  }
6825
8133
  let count = parseInt(countStr);
6826
8134
  if (isNaN(count) || count <= 0) {
6827
- const res = await input8({
8135
+ const res = await input11({
6828
8136
  message: "How many rows to generate?",
6829
8137
  default: "50"
6830
8138
  });
@@ -6883,19 +8191,19 @@ async function runTruncateCommand(dbConfig, args) {
6883
8191
  }
6884
8192
  const adapter = createDBAdapter(dbConfig);
6885
8193
  let tableName = args[0];
6886
- const { select: select13, confirm: confirm6 } = await import("@inquirer/prompts");
8194
+ const { select: select16, confirm: confirm8 } = await import("@inquirer/prompts");
6887
8195
  if (!tableName) {
6888
8196
  const allTables = await adapter.getTables();
6889
8197
  if (allTables.length === 0) {
6890
8198
  console.log(pc6.yellow("No tables found in the database."));
6891
8199
  process.exit(0);
6892
8200
  }
6893
- tableName = await select13({
8201
+ tableName = await select16({
6894
8202
  message: "Which table do you want to truncate (empty all data)?",
6895
8203
  choices: allTables.map((t) => ({ name: t, value: t }))
6896
8204
  });
6897
8205
  }
6898
- const sure = await confirm6({
8206
+ const sure = await confirm8({
6899
8207
  message: `Are you sure you want to TRUNCATE '${tableName}'? This will delete all rows and cannot be undone!`,
6900
8208
  default: false
6901
8209
  });
@@ -6921,8 +8229,8 @@ async function runTruncateCommand(dbConfig, args) {
6921
8229
  // src/commands/exec.ts
6922
8230
  init_logic();
6923
8231
  import pc7 from "picocolors";
6924
- import fs7 from "fs/promises";
6925
- import path7 from "path";
8232
+ import fs8 from "fs/promises";
8233
+ import path8 from "path";
6926
8234
  async function runExecCommand(dbConfig, args) {
6927
8235
  if (dbConfig.type === "unknown") {
6928
8236
  console.log(
@@ -6931,18 +8239,18 @@ async function runExecCommand(dbConfig, args) {
6931
8239
  process.exit(1);
6932
8240
  }
6933
8241
  let filePath = args[0];
6934
- const { input: input8 } = await import("@inquirer/prompts");
8242
+ const { input: input11 } = await import("@inquirer/prompts");
6935
8243
  if (!filePath) {
6936
- filePath = await input8({ message: "Enter the path to your .sql file:" });
8244
+ filePath = await input11({ message: "Enter the path to your .sql file:" });
6937
8245
  }
6938
- const resolvedPath = path7.resolve(process.cwd(), filePath);
8246
+ const resolvedPath = path8.resolve(process.cwd(), filePath);
6939
8247
  try {
6940
- await fs7.access(resolvedPath);
8248
+ await fs8.access(resolvedPath);
6941
8249
  } catch {
6942
8250
  console.log(pc7.red(`Error: File not found at ${resolvedPath}`));
6943
8251
  process.exit(1);
6944
8252
  }
6945
- const sqlContent = await fs7.readFile(resolvedPath, "utf-8");
8253
+ const sqlContent = await fs8.readFile(resolvedPath, "utf-8");
6946
8254
  if (!sqlContent.trim()) {
6947
8255
  console.log(pc7.yellow("File is empty."));
6948
8256
  process.exit(0);
@@ -7021,8 +8329,8 @@ Backup Error: ${e.message}
7021
8329
  // src/commands/restore.ts
7022
8330
  init_logic();
7023
8331
  import pc9 from "picocolors";
7024
- import fs8 from "fs/promises";
7025
- import path8 from "path";
8332
+ import fs9 from "fs/promises";
8333
+ import path9 from "path";
7026
8334
  async function runRestoreCommand(dbConfig, args, options = {}) {
7027
8335
  if (dbConfig.type === "unknown") {
7028
8336
  console.log(
@@ -7031,49 +8339,33 @@ async function runRestoreCommand(dbConfig, args, options = {}) {
7031
8339
  process.exit(1);
7032
8340
  }
7033
8341
  let targetPath = args[0];
7034
- const { input: input8, select: select13, confirm: confirm6 } = await import("@inquirer/prompts");
8342
+ const { input: input11, select: select16, confirm: confirm8 } = await import("@inquirer/prompts");
7035
8343
  if (!targetPath) {
7036
8344
  try {
7037
- const entries = await fs8.readdir(process.cwd(), {
7038
- withFileTypes: true
7039
- });
7040
- const candidates = [];
7041
- for (const entry of entries) {
7042
- if (entry.isDirectory() && entry.name.startsWith("drixio_backup_")) {
7043
- candidates.push({
7044
- name: `\u{1F4C1} ${entry.name} (Backup Directory)`,
7045
- value: entry.name
7046
- });
7047
- } else if (entry.isFile() && (entry.name.endsWith(".sql") || entry.name.endsWith(".json"))) {
7048
- candidates.push({
7049
- name: `\u{1F4C4} ${entry.name} (${entry.name.endsWith(".sql") ? "SQL Script" : "JSON Dump"})`,
7050
- value: entry.name
7051
- });
7052
- }
7053
- }
8345
+ const candidates = await getRestoreCandidates(process.cwd());
7054
8346
  if (candidates.length > 0) {
7055
8347
  candidates.push({
7056
8348
  name: "\u270F\uFE0F Enter a custom path manually...",
7057
8349
  value: "__custom__"
7058
8350
  });
7059
- const choice = await select13({
8351
+ const choice = await select16({
7060
8352
  message: "Select a backup directory or file to restore:",
7061
8353
  choices: candidates
7062
8354
  });
7063
8355
  if (choice === "__custom__") {
7064
- targetPath = await input8({
8356
+ targetPath = await input11({
7065
8357
  message: "Enter path to backup directory, .sql, or .json file:"
7066
8358
  });
7067
8359
  } else {
7068
8360
  targetPath = choice;
7069
8361
  }
7070
8362
  } else {
7071
- targetPath = await input8({
8363
+ targetPath = await input11({
7072
8364
  message: "Enter path to backup directory, .sql, or .json file:"
7073
8365
  });
7074
8366
  }
7075
8367
  } catch {
7076
- targetPath = await input8({
8368
+ targetPath = await input11({
7077
8369
  message: "Enter path to backup directory, .sql, or .json file:"
7078
8370
  });
7079
8371
  }
@@ -7082,9 +8374,9 @@ async function runRestoreCommand(dbConfig, args, options = {}) {
7082
8374
  console.log(pc9.yellow("No restore source specified. Aborting."));
7083
8375
  process.exit(0);
7084
8376
  }
7085
- const resolvedPath = path8.resolve(process.cwd(), targetPath.trim());
8377
+ const resolvedPath = path9.resolve(process.cwd(), targetPath.trim());
7086
8378
  try {
7087
- await fs8.access(resolvedPath);
8379
+ await fs9.access(resolvedPath);
7088
8380
  } catch {
7089
8381
  console.log(pc9.red(`Error: Backup path not found: ${resolvedPath}`));
7090
8382
  process.exit(1);
@@ -7100,7 +8392,7 @@ async function runRestoreCommand(dbConfig, args, options = {}) {
7100
8392
  console.log(pc9.dim(`Database: ${dbConfig.type} (${dbConfig.targetUrl})`));
7101
8393
  console.log(pc9.dim(`Source: ${resolvedPath}
7102
8394
  `));
7103
- const proceed = await confirm6({
8395
+ const proceed = await confirm8({
7104
8396
  message: "Are you sure you want to proceed with the database restore?",
7105
8397
  default: false
7106
8398
  });
@@ -7147,8 +8439,8 @@ Starting database restore from: ${resolvedPath}...`));
7147
8439
  // src/commands/diagram.ts
7148
8440
  init_logic();
7149
8441
  import pc10 from "picocolors";
7150
- import fs9 from "fs/promises";
7151
- import path9 from "path";
8442
+ import fs10 from "fs/promises";
8443
+ import path10 from "path";
7152
8444
  async function runDiagramCommand(dbConfig) {
7153
8445
  if (dbConfig.type === "unknown") {
7154
8446
  console.log(
@@ -7186,8 +8478,8 @@ You can paste this Mermaid code directly into [Draw.io](https://app.diagrams.net
7186
8478
  ${mermaidCode}
7187
8479
  \`\`\`
7188
8480
  `;
7189
- const outPath = path9.resolve(process.cwd(), "drixio_schema.md");
7190
- await fs9.writeFile(outPath, markdownOutput.trim(), "utf-8");
8481
+ const outPath = path10.resolve(process.cwd(), "drixio_schema.md");
8482
+ await fs10.writeFile(outPath, markdownOutput.trim(), "utf-8");
7191
8483
  console.log(pc10.green(`
7192
8484
  \u2714 Diagram generated successfully!`));
7193
8485
  console.log(pc10.white(`Output saved to: ${pc10.bold(outPath)}`));
@@ -7208,8 +8500,8 @@ ${mermaidCode}
7208
8500
  // src/commands/generateTypes.ts
7209
8501
  init_logic();
7210
8502
  import pc11 from "picocolors";
7211
- import fs10 from "fs/promises";
7212
- import path10 from "path";
8503
+ import fs11 from "fs/promises";
8504
+ import path11 from "path";
7213
8505
  async function runGenerateTypesCommand(dbConfig) {
7214
8506
  if (dbConfig.type === "unknown") {
7215
8507
  console.log(
@@ -7233,8 +8525,8 @@ Scanning database to generate TypeScript interfaces...`)
7233
8525
  process.exit(0);
7234
8526
  }
7235
8527
  const tsCode = generateTypeScriptDefinitions(tableInfos, dbConfig.type);
7236
- const outPath = path10.resolve(process.cwd(), "drixio-types.d.ts");
7237
- await fs10.writeFile(outPath, tsCode, "utf-8");
8528
+ const outPath = path11.resolve(process.cwd(), "drixio-types.d.ts");
8529
+ await fs11.writeFile(outPath, tsCode, "utf-8");
7238
8530
  console.log(pc11.green(`\u2714 TypeScript interfaces generated successfully!`));
7239
8531
  console.log(pc11.white(`Output saved to: ${pc11.bold(outPath)}`));
7240
8532
  } catch (e) {
@@ -7249,8 +8541,8 @@ Scanning database to generate TypeScript interfaces...`)
7249
8541
  // src/commands/generateOrm.ts
7250
8542
  init_logic();
7251
8543
  import pc12 from "picocolors";
7252
- import fs11 from "fs/promises";
7253
- import path11 from "path";
8544
+ import fs12 from "fs/promises";
8545
+ import path12 from "path";
7254
8546
  async function runGenerateOrmCommand(dbConfig, options = {}) {
7255
8547
  if (dbConfig.type === "unknown") {
7256
8548
  console.log(
@@ -7306,11 +8598,11 @@ Scanning database to generate ${target === "prisma" ? "Prisma" : "Drizzle"} sche
7306
8598
  if (options.print) {
7307
8599
  console.log("\n" + generatedCode);
7308
8600
  } else {
7309
- const outPath = path11.resolve(
8601
+ const outPath = path12.resolve(
7310
8602
  process.cwd(),
7311
8603
  options.out || defaultFileName
7312
8604
  );
7313
- await fs11.writeFile(outPath, generatedCode, "utf-8");
8605
+ await fs12.writeFile(outPath, generatedCode, "utf-8");
7314
8606
  console.log(
7315
8607
  pc12.green(
7316
8608
  `\u2714 ${target === "prisma" ? "Prisma" : "Drizzle"} schema generated successfully!`
@@ -7331,10 +8623,10 @@ Scanning database to generate ${target === "prisma" ? "Prisma" : "Drizzle"} sche
7331
8623
  init_logic();
7332
8624
  import pc13 from "picocolors";
7333
8625
  async function runInitCommand(args) {
7334
- const { select: select13, input: input8, password } = await import("@inquirer/prompts");
8626
+ const { select: select16, input: input11, password } = await import("@inquirer/prompts");
7335
8627
  let dialect = args[0];
7336
8628
  if (!dialect || !["sqlite", "mysql", "postgres"].includes(dialect.toLowerCase())) {
7337
- dialect = await select13({
8629
+ dialect = await select16({
7338
8630
  message: "Which database do you want to initialize locally?",
7339
8631
  choices: [
7340
8632
  { name: "SQLite (Local File)", value: "sqlite" },
@@ -7380,15 +8672,15 @@ Initializing a local ${dialect} database...`));
7380
8672
  "Please provide credentials for your local server (e.g. running via XAMPP, Homebrew, etc.)"
7381
8673
  )
7382
8674
  );
7383
- const host = await input8({
8675
+ const host = await input11({
7384
8676
  message: "Server Host:",
7385
8677
  default: "localhost"
7386
8678
  });
7387
- const port = await input8({
8679
+ const port = await input11({
7388
8680
  message: "Server Port:",
7389
8681
  default: dialect === "mysql" ? "3306" : "5432"
7390
8682
  });
7391
- const user = await input8({
8683
+ const user = await input11({
7392
8684
  message: "Username:",
7393
8685
  default: dialect === "mysql" ? "root" : "postgres"
7394
8686
  });
@@ -7397,7 +8689,7 @@ Initializing a local ${dialect} database...`));
7397
8689
  });
7398
8690
  let dbName = args[1];
7399
8691
  if (!dbName) {
7400
- dbName = await input8({
8692
+ dbName = await input11({
7401
8693
  message: "New Database Name (e.g. my_project):"
7402
8694
  });
7403
8695
  }
@@ -7489,10 +8781,10 @@ Could not connect to the local ${dialect} server on ${host}:${port}.`
7489
8781
  init_logic();
7490
8782
  import pc14 from "picocolors";
7491
8783
  async function runDropDbCommand(args) {
7492
- const { select: select13, input: input8, password, confirm: confirm6 } = await import("@inquirer/prompts");
8784
+ const { select: select16, input: input11, password, confirm: confirm8 } = await import("@inquirer/prompts");
7493
8785
  let dialect = args[0];
7494
8786
  if (!dialect || !["sqlite", "mysql", "postgres"].includes(dialect.toLowerCase())) {
7495
- dialect = await select13({
8787
+ dialect = await select16({
7496
8788
  message: "Which database type do you want to drop?",
7497
8789
  choices: [
7498
8790
  { name: "SQLite (Local File)", value: "sqlite" },
@@ -7505,12 +8797,12 @@ async function runDropDbCommand(args) {
7505
8797
  let dbName = args[1];
7506
8798
  if (dialect === "sqlite") {
7507
8799
  if (!dbName) {
7508
- dbName = await input8({
8800
+ dbName = await input11({
7509
8801
  message: "Enter the SQLite filename to delete (e.g. database.sqlite):",
7510
8802
  default: "database.sqlite"
7511
8803
  });
7512
8804
  }
7513
- const sure = await confirm6({
8805
+ const sure = await confirm8({
7514
8806
  message: `Are you absolutely sure you want to delete ${dbName}? This cannot be undone!`,
7515
8807
  default: false
7516
8808
  });
@@ -7535,15 +8827,15 @@ async function runDropDbCommand(args) {
7535
8827
  `Please provide credentials for your local ${dialect} server to drop a database.`
7536
8828
  )
7537
8829
  );
7538
- const host = await input8({
8830
+ const host = await input11({
7539
8831
  message: "Server Host:",
7540
8832
  default: "localhost"
7541
8833
  });
7542
- const port = await input8({
8834
+ const port = await input11({
7543
8835
  message: "Server Port:",
7544
8836
  default: dialect === "mysql" ? "3306" : "5432"
7545
8837
  });
7546
- const user = await input8({
8838
+ const user = await input11({
7547
8839
  message: "Username:",
7548
8840
  default: dialect === "mysql" ? "root" : "postgres"
7549
8841
  });
@@ -7551,11 +8843,11 @@ async function runDropDbCommand(args) {
7551
8843
  message: "Password (leave empty if none):"
7552
8844
  });
7553
8845
  if (!dbName) {
7554
- dbName = await input8({
8846
+ dbName = await input11({
7555
8847
  message: "Which Database Name do you want to DROP?"
7556
8848
  });
7557
8849
  }
7558
- const sure = await confirm6({
8850
+ const sure = await confirm8({
7559
8851
  message: `Are you absolutely sure you want to DROP DATABASE '${dbName}' from ${host}? All data will be lost!`,
7560
8852
  default: false
7561
8853
  });
@@ -7676,8 +8968,8 @@ async function runDescribeCommand(dbConfig, args, options = {}) {
7676
8968
  if (tables.length === 1) {
7677
8969
  tableName = tables[0];
7678
8970
  } else {
7679
- const { select: select13 } = await import("@inquirer/prompts");
7680
- tableName = await select13({
8971
+ const { select: select16 } = await import("@inquirer/prompts");
8972
+ tableName = await select16({
7681
8973
  message: "Select a table to describe:",
7682
8974
  choices: tables.map((t) => ({ name: t, value: t }))
7683
8975
  });
@@ -7766,9 +9058,9 @@ Failed to describe table: ${e.message}
7766
9058
  // src/commands/diff.ts
7767
9059
  init_logic();
7768
9060
  import pc17 from "picocolors";
7769
- import fs12 from "fs/promises";
9061
+ import fs13 from "fs/promises";
7770
9062
  import { existsSync as existsSync2 } from "fs";
7771
- import path12 from "path";
9063
+ import path13 from "path";
7772
9064
  async function runDiffCommand(dbConfig, args, options = {}) {
7773
9065
  if (dbConfig.type === "unknown") {
7774
9066
  console.log(
@@ -7789,7 +9081,7 @@ Capturing schema snapshot for ${dbConfig.type.toUpperCase()} database...`
7789
9081
  const snapshotRes = await createSchemaSnapshot(
7790
9082
  adapter,
7791
9083
  dbConfig.type,
7792
- path12.basename(outPath, ".json")
9084
+ path13.basename(outPath, ".json")
7793
9085
  );
7794
9086
  if (!snapshotRes.success) {
7795
9087
  console.log(
@@ -7797,9 +9089,9 @@ Capturing schema snapshot for ${dbConfig.type.toUpperCase()} database...`
7797
9089
  );
7798
9090
  process.exit(1);
7799
9091
  }
7800
- const resolvedPath = path12.resolve(process.cwd(), outPath);
7801
- await fs12.mkdir(path12.dirname(resolvedPath), { recursive: true });
7802
- await fs12.writeFile(
9092
+ const resolvedPath = path13.resolve(process.cwd(), outPath);
9093
+ await fs13.mkdir(path13.dirname(resolvedPath), { recursive: true });
9094
+ await fs13.writeFile(
7803
9095
  resolvedPath,
7804
9096
  JSON.stringify(snapshotRes.data, null, 2),
7805
9097
  "utf-8"
@@ -7816,9 +9108,9 @@ Capturing schema snapshot for ${dbConfig.type.toUpperCase()} database...`
7816
9108
  }
7817
9109
  let targetArg = args[0];
7818
9110
  if (!targetArg) {
7819
- if (existsSync2(path12.resolve(process.cwd(), "schema.json"))) {
9111
+ if (existsSync2(path13.resolve(process.cwd(), "schema.json"))) {
7820
9112
  targetArg = "schema.json";
7821
- } else if (existsSync2(path12.resolve(process.cwd(), ".drixio/schema.json"))) {
9113
+ } else if (existsSync2(path13.resolve(process.cwd(), ".drixio/schema.json"))) {
7822
9114
  targetArg = ".drixio/schema.json";
7823
9115
  } else {
7824
9116
  console.log(
@@ -7845,7 +9137,7 @@ Usage examples:`));
7845
9137
  }
7846
9138
  }
7847
9139
  let diffResult;
7848
- const isRemoteUrl = targetArg.startsWith("postgres://") || targetArg.startsWith("postgresql://") || targetArg.startsWith("mysql://") || targetArg.startsWith("file:");
9140
+ const isRemoteUrl = isConnectionString(targetArg) || targetArg.startsWith("file:");
7849
9141
  if (isRemoteUrl) {
7850
9142
  console.log(
7851
9143
  pc17.cyan(
@@ -7873,7 +9165,7 @@ Connecting to target database and computing schema diff...`
7873
9165
  });
7874
9166
  }
7875
9167
  } else {
7876
- const filePath = path12.resolve(process.cwd(), targetArg);
9168
+ const filePath = path13.resolve(process.cwd(), targetArg);
7877
9169
  if (!existsSync2(filePath)) {
7878
9170
  console.log(
7879
9171
  pc17.red(`\u2718 Target snapshot file not found: ${targetArg}`)
@@ -7886,7 +9178,7 @@ Connecting to target database and computing schema diff...`
7886
9178
  Comparing current database with snapshot: ${targetArg}...`
7887
9179
  )
7888
9180
  );
7889
- const content = await fs12.readFile(filePath, "utf-8");
9181
+ const content = await fs13.readFile(filePath, "utf-8");
7890
9182
  let snapshot;
7891
9183
  try {
7892
9184
  snapshot = JSON.parse(content);
@@ -7901,7 +9193,7 @@ Comparing current database with snapshot: ${targetArg}...`
7901
9193
  snapshot,
7902
9194
  dbConfig.type,
7903
9195
  "Current Database",
7904
- `Snapshot (${path12.basename(targetArg)})`
9196
+ `Snapshot (${path13.basename(targetArg)})`
7905
9197
  );
7906
9198
  if (!diffRes.success) {
7907
9199
  console.log(pc17.red(`\u2718 Diff failed: ${diffRes.error}`));
@@ -7916,9 +9208,9 @@ Comparing current database with snapshot: ${targetArg}...`
7916
9208
  printDiffReport(diffResult, options.reverse);
7917
9209
  const targetSql = options.reverse ? diffResult.rollbackSql : diffResult.migrationSql;
7918
9210
  if (options.out) {
7919
- const outResolved = path12.resolve(process.cwd(), options.out);
7920
- await fs12.mkdir(path12.dirname(outResolved), { recursive: true });
7921
- await fs12.writeFile(outResolved, targetSql, "utf-8");
9211
+ const outResolved = path13.resolve(process.cwd(), options.out);
9212
+ await fs13.mkdir(path13.dirname(outResolved), { recursive: true });
9213
+ await fs13.writeFile(outResolved, targetSql, "utf-8");
7922
9214
  console.log(
7923
9215
  pc17.green(`\u2714 Migration SQL written to: ${pc17.bold(options.out)}`)
7924
9216
  );
@@ -7934,8 +9226,8 @@ Nothing to apply. Schema is already in sync.`)
7934
9226
  const skipConfirm = options.force || options.y;
7935
9227
  let proceed = skipConfirm;
7936
9228
  if (!proceed) {
7937
- const { confirm: confirm6 } = await import("@inquirer/prompts");
7938
- proceed = await confirm6({
9229
+ const { confirm: confirm8 } = await import("@inquirer/prompts");
9230
+ proceed = await confirm8({
7939
9231
  message: `Apply this ${options.reverse ? "rollback" : "migration"} to "${dbConfig.type.toUpperCase()}" database?`,
7940
9232
  default: false
7941
9233
  });
@@ -8107,8 +9399,8 @@ async function runRunCommand(dbConfig, args, _options) {
8107
9399
  const targetIdent = args[0];
8108
9400
  const paramArgs = args.slice(1);
8109
9401
  if (!targetIdent) {
8110
- const { select: select13 } = await import("@inquirer/prompts");
8111
- const selectedId = await select13({
9402
+ const { select: select16 } = await import("@inquirer/prompts");
9403
+ const selectedId = await select16({
8112
9404
  message: "Select a saved query template to run:",
8113
9405
  choices: snippets.map((s) => ({
8114
9406
  name: `${s.title} ${pc18.dim(`(${s.id})`)}`,
@@ -8145,10 +9437,10 @@ async function runRunCommand(dbConfig, args, _options) {
8145
9437
  }
8146
9438
  }
8147
9439
  if (requiredParams.length > 0) {
8148
- const { input: input8 } = await import("@inquirer/prompts");
9440
+ const { input: input11 } = await import("@inquirer/prompts");
8149
9441
  for (const param of requiredParams) {
8150
9442
  if (providedParams[param] === void 0) {
8151
- const val = await input8({
9443
+ const val = await input11({
8152
9444
  message: `Enter value for :${param}:`,
8153
9445
  validate: (v) => v.trim() !== "" ? true : `:${param} cannot be empty`
8154
9446
  });
@@ -8270,7 +9562,7 @@ async function runQuickCommand(command, args, options, dbConfig) {
8270
9562
  // src/tui/index.ts
8271
9563
  init_logo();
8272
9564
  init_logic();
8273
- import pc34 from "picocolors";
9565
+ import pc37 from "picocolors";
8274
9566
 
8275
9567
  // src/tui/views/editor.ts
8276
9568
  init_logic();
@@ -8302,8 +9594,8 @@ var selectTable = async (tableChoices) => await select({
8302
9594
  init_logic();
8303
9595
  import { input, select as select2 } from "@inquirer/prompts";
8304
9596
  import pc21 from "picocolors";
8305
- import fs13 from "fs/promises";
8306
- import path13 from "path";
9597
+ import fs14 from "fs/promises";
9598
+ import path14 from "path";
8307
9599
  async function runBeginnerAdd(adapter, dbType, tableName, columns) {
8308
9600
  console.log(pc21.cyan(`
8309
9601
  --- Add Data to [${tableName}] ---`));
@@ -8423,10 +9715,10 @@ async function runExpertMode(adapter) {
8423
9715
  message: "Enter the path to your .md or .sql file:"
8424
9716
  });
8425
9717
  if (filePath && filePath.trim()) {
8426
- const absolutePath = path13.resolve(process.cwd(), filePath.trim());
9718
+ const absolutePath = path14.resolve(process.cwd(), filePath.trim());
8427
9719
  let fileContent = "";
8428
9720
  try {
8429
- fileContent = await fs13.readFile(absolutePath, "utf-8");
9721
+ fileContent = await fs14.readFile(absolutePath, "utf-8");
8430
9722
  } catch (err2) {
8431
9723
  console.log(pc21.red(`
8432
9724
  x Failed to read file: ${err2.message}`));
@@ -8543,8 +9835,8 @@ async function viewTables(dbConfig) {
8543
9835
  Error fetching data: ${e.message}
8544
9836
  `));
8545
9837
  currentWhere = "";
8546
- const { input: input8 } = await import("@inquirer/prompts");
8547
- await input8({ message: "Click Enter to continue..." });
9838
+ const { input: input11 } = await import("@inquirer/prompts");
9839
+ await input11({ message: "Click Enter to continue..." });
8548
9840
  continue;
8549
9841
  }
8550
9842
  const rows = data.rows;
@@ -8613,8 +9905,8 @@ Error fetching data: ${e.message}
8613
9905
  continue;
8614
9906
  }
8615
9907
  if (action === "search") {
8616
- const { input: input8 } = await import("@inquirer/prompts");
8617
- const searchInput = await input8({
9908
+ const { input: input11 } = await import("@inquirer/prompts");
9909
+ const searchInput = await input11({
8618
9910
  message: "Enter Search (e.g. `age > 18` or `John` for fuzzy search):"
8619
9911
  });
8620
9912
  currentWhere = buildSearchWhereClause(
@@ -8638,18 +9930,18 @@ Error fetching data: ${e.message}
8638
9930
  console.log(pc22.red(`
8639
9931
  Export Error: ${exportRes.error}`));
8640
9932
  } else {
8641
- const fs16 = await import("fs/promises");
8642
- const path17 = await import("path");
8643
- const exportDir = path17.join(
9933
+ const fs19 = await import("fs/promises");
9934
+ const path20 = await import("path");
9935
+ const exportDir = path20.join(
8644
9936
  process.cwd(),
8645
9937
  "drixio_exports"
8646
9938
  );
8647
- await fs16.mkdir(exportDir, { recursive: true });
8648
- const fp = path17.join(
9939
+ await fs19.mkdir(exportDir, { recursive: true });
9940
+ const fp = path20.join(
8649
9941
  exportDir,
8650
9942
  `${selectedTable}.${format}`
8651
9943
  );
8652
- await fs16.writeFile(fp, exportRes.data, "utf-8");
9944
+ await fs19.writeFile(fp, exportRes.data, "utf-8");
8653
9945
  console.log(pc22.green(`
8654
9946
  \u2714 Exported to ${fp}`));
8655
9947
  }
@@ -8657,8 +9949,8 @@ Export Error: ${exportRes.error}`));
8657
9949
  console.log(pc22.red(`
8658
9950
  Export Error: ${e.message}`));
8659
9951
  }
8660
- const { input: input8 } = await import("@inquirer/prompts");
8661
- await input8({ message: "Click Enter to continue..." });
9952
+ const { input: input11 } = await import("@inquirer/prompts");
9953
+ await input11({ message: "Click Enter to continue..." });
8662
9954
  continue;
8663
9955
  }
8664
9956
  const mode = await select3({
@@ -8720,8 +10012,8 @@ x Error: ${error.message}`));
8720
10012
  await adapter.close();
8721
10013
  }
8722
10014
  async function waitForEnter() {
8723
- const { input: input8 } = await import("@inquirer/prompts");
8724
- await input8({
10015
+ const { input: input11 } = await import("@inquirer/prompts");
10016
+ await input11({
8725
10017
  message: "Press Enter to continue..."
8726
10018
  });
8727
10019
  }
@@ -8837,6 +10129,24 @@ var selectAction = async (dbConfig) => await select5({
8837
10129
  description: "Create new tables, or modify/drop existing tables.",
8838
10130
  disabled: dbConfig.type === "unknown"
8839
10131
  },
10132
+ {
10133
+ name: "\u26A1 ORM & Type Generator",
10134
+ value: "orm",
10135
+ description: "Generate Prisma Schema, Drizzle models, or TypeScript definitions.",
10136
+ disabled: dbConfig.type === "unknown"
10137
+ },
10138
+ {
10139
+ name: "\u{1F4D1} SQL Snippets & Templates",
10140
+ value: "snippets",
10141
+ description: "Run parameterized diagnostic queries and high-frequency templates.",
10142
+ disabled: dbConfig.type === "unknown"
10143
+ },
10144
+ {
10145
+ name: "\u{1F504} Schema Diff & Migrations",
10146
+ value: "diff",
10147
+ description: "Compare database schemas with snapshots and generate migration SQL.",
10148
+ disabled: dbConfig.type === "unknown"
10149
+ },
8840
10150
  new Separator2(),
8841
10151
  {
8842
10152
  name: dbConfig.type === "unknown" ? " Setup Connection" : " Connection Settings",
@@ -8858,7 +10168,7 @@ async function runRepl(dbConfig) {
8858
10168
  if (dbConfig.type === "unknown") return;
8859
10169
  const dbAdapter = createDBAdapter(dbConfig);
8860
10170
  let running = true;
8861
- const { input: input8 } = await import("@inquirer/prompts");
10171
+ const { input: input11 } = await import("@inquirer/prompts");
8862
10172
  console.clear();
8863
10173
  console.log(
8864
10174
  pc25.cyan(
@@ -8889,7 +10199,7 @@ async function runRepl(dbConfig) {
8889
10199
  );
8890
10200
  while (running) {
8891
10201
  try {
8892
- const queryStr = await input8({
10202
+ const queryStr = await input11({
8893
10203
  message: pc25.green(`${dbConfig.type}>`)
8894
10204
  });
8895
10205
  const sql = queryStr.trim();
@@ -8935,7 +10245,7 @@ async function runTui(initialConfig, customUrl) {
8935
10245
  case "editor":
8936
10246
  if (dbConfig.type === "unknown") {
8937
10247
  console.log(
8938
- pc34.yellow(
10248
+ pc37.yellow(
8939
10249
  `
8940
10250
  No database connection found. Please setup database first.`
8941
10251
  )
@@ -8952,6 +10262,24 @@ No database connection found. Please setup database first.`
8952
10262
  case "repl":
8953
10263
  await runRepl(dbConfig);
8954
10264
  break;
10265
+ case "orm": {
10266
+ const { runOrmWizard: runOrmWizard2 } = await Promise.resolve().then(() => (init_ormWizard(), ormWizard_exports));
10267
+ await runOrmWizard2(dbConfig);
10268
+ await waitForEnter4();
10269
+ break;
10270
+ }
10271
+ case "snippets": {
10272
+ const { runSnippetsWizard: runSnippetsWizard2 } = await Promise.resolve().then(() => (init_snippetsWizard(), snippetsWizard_exports));
10273
+ await runSnippetsWizard2(dbConfig);
10274
+ await waitForEnter4();
10275
+ break;
10276
+ }
10277
+ case "diff": {
10278
+ const { runDiffWizard: runDiffWizard2 } = await Promise.resolve().then(() => (init_diffWizard(), diffWizard_exports));
10279
+ await runDiffWizard2(dbConfig);
10280
+ await waitForEnter4();
10281
+ break;
10282
+ }
8955
10283
  case "setup":
8956
10284
  case "re-configure":
8957
10285
  dbConfig = await runSetup(dbConfig);
@@ -8959,32 +10287,19 @@ No database connection found. Please setup database first.`
8959
10287
  break;
8960
10288
  case "exit":
8961
10289
  running = false;
8962
- console.log(pc34.dim("\nThanks for using Drixio. Goodbye!"));
10290
+ console.log(pc37.dim("\nThanks for using Drixio. Goodbye!"));
8963
10291
  break;
8964
10292
  }
8965
10293
  }
8966
10294
  async function waitForEnter4() {
8967
- const { input: input8 } = await import("@inquirer/prompts");
8968
- await input8({
10295
+ const { input: input11 } = await import("@inquirer/prompts");
10296
+ await input11({
8969
10297
  message: "Click Enter to continue..."
8970
10298
  });
8971
10299
  }
8972
10300
  }
8973
10301
 
8974
10302
  // bin/cli.ts
8975
- import { readFileSync } from "fs";
8976
- import { fileURLToPath as fileURLToPath2 } from "url";
8977
- import { dirname, join } from "path";
8978
- function getVersion() {
8979
- try {
8980
- const __dirname2 = dirname(fileURLToPath2(import.meta.url));
8981
- const pkgPath = join(__dirname2, "../package.json");
8982
- const pkg = JSON.parse(readFileSync(pkgPath, "utf8"));
8983
- return pkg.version || "1.1.9";
8984
- } catch {
8985
- return "1.1.9";
8986
- }
8987
- }
8988
10303
  async function main() {
8989
10304
  const args = process.argv.slice(2);
8990
10305
  let customUrl;
@@ -9012,80 +10327,80 @@ async function main() {
9012
10327
  allowPositionals: true
9013
10328
  });
9014
10329
  if (values.version) {
9015
- console.log(`drixio v${getVersion()}`);
10330
+ console.log(`drixio v${getDrixioVersion()}`);
9016
10331
  process.exit(0);
9017
10332
  }
9018
10333
  if (values.help) {
9019
- console.log(pc36.cyan(`
10334
+ console.log(pc39.cyan(`
9020
10335
  Drixio - Modern Database Manager
9021
10336
  `));
9022
- console.log(`${pc36.bold("Usage:")} npx drixio [command] [options]
10337
+ console.log(`${pc39.bold("Usage:")} npx drixio [command] [options]
9023
10338
  `);
9024
- console.log(`${pc36.bold("Tiers & Interfaces:")}`);
10339
+ console.log(`${pc39.bold("Tiers & Interfaces:")}`);
9025
10340
  console.log(
9026
- ` Interactive TUI: ${pc36.green("npx drixio")} (Terminal UI with beginner wizards)`
10341
+ ` Interactive TUI: ${pc39.green("npx drixio")} (Terminal UI with beginner wizards)`
9027
10342
  );
9028
10343
  console.log(
9029
- ` Web Studio: ${pc36.green("npx drixio studio")} (Browser UI for bulk inspection)`
10344
+ ` Web Studio: ${pc39.green("npx drixio studio")} (Browser UI for bulk inspection)`
9030
10345
  );
9031
10346
  console.log(`
9032
- ${pc36.bold("Quick Commands (Headless / Pro):")}`);
10347
+ ${pc39.bold("Quick Commands (Headless / Pro):")}`);
9033
10348
  console.log(
9034
- ` ${pc36.green("tables")} List all database tables and row counts (alias: ls)`
10349
+ ` ${pc39.green("tables")} List all database tables and row counts (alias: ls)`
9035
10350
  );
9036
10351
  console.log(
9037
- ` ${pc36.green("describe")} [table] Inspect columns, types, PKs, and indexes (alias: desc)`
10352
+ ` ${pc39.green("describe")} [table] Inspect columns, types, PKs, and indexes (alias: desc)`
9038
10353
  );
9039
10354
  console.log(
9040
- ` ${pc36.green("query")} "<sql>" Run a quick SQL query`
10355
+ ` ${pc39.green("query")} "<sql>" Run a quick SQL query`
9041
10356
  );
9042
10357
  console.log(
9043
- ` ${pc36.green("exec")} <file.sql> Execute a SQL script file`
10358
+ ` ${pc39.green("exec")} <file.sql> Execute a SQL script file`
9044
10359
  );
9045
10360
  console.log(
9046
- ` ${pc36.green("export")} [table] Export table(s) to CSV/JSON`
10361
+ ` ${pc39.green("export")} [table] Export table(s) to CSV/JSON`
9047
10362
  );
9048
10363
  console.log(
9049
- ` ${pc36.green("import")} [file] Import JSON/CSV into a table`
10364
+ ` ${pc39.green("import")} [file] Import JSON/CSV into a table`
9050
10365
  );
9051
10366
  console.log(
9052
- ` ${pc36.green("seed")} [table] [count] Generate realistic fake data for a table`
10367
+ ` ${pc39.green("seed")} [table] [count] Generate realistic fake data for a table`
9053
10368
  );
9054
10369
  console.log(
9055
- ` ${pc36.green("truncate")} [table] Empty all data in a table and reset sequence`
10370
+ ` ${pc39.green("truncate")} [table] Empty all data in a table and reset sequence`
9056
10371
  );
9057
10372
  console.log(
9058
- ` ${pc36.green("diagram")} Generate a Mermaid ER diagram`
10373
+ ` ${pc39.green("diagram")} Generate a Mermaid ER diagram`
9059
10374
  );
9060
10375
  console.log(
9061
- ` ${pc36.green("generate-types")} Generate TypeScript interfaces`
10376
+ ` ${pc39.green("generate-types")} Generate TypeScript interfaces`
9062
10377
  );
9063
10378
  console.log(
9064
- ` ${pc36.green("generate-orm")} [target] Generate Prisma or Drizzle ORM schema`
10379
+ ` ${pc39.green("generate-orm")} [target] Generate Prisma or Drizzle ORM schema`
9065
10380
  );
9066
10381
  console.log(
9067
- ` ${pc36.green("diff")} [target] Compare schemas & generate migration SQL`
10382
+ ` ${pc39.green("diff")} [target] Compare schemas & generate migration SQL`
9068
10383
  );
9069
10384
  console.log(
9070
- ` ${pc36.green("snippets")} List saved SQL snippets & templates (alias: snip)`
10385
+ ` ${pc39.green("snippets")} List saved SQL snippets & templates (alias: snip)`
9071
10386
  );
9072
10387
  console.log(
9073
- ` ${pc36.green("run")} [snippet] Execute a saved snippet or parametric query`
10388
+ ` ${pc39.green("run")} [snippet] Execute a saved snippet or parametric query`
9074
10389
  );
9075
10390
  console.log(
9076
- ` ${pc36.green("backup")} Backup the entire database`
10391
+ ` ${pc39.green("backup")} Backup the entire database`
9077
10392
  );
9078
10393
  console.log(
9079
- ` ${pc36.green("restore")} [dir|file] Restore database from a backup directory or .sql file`
10394
+ ` ${pc39.green("restore")} [dir|file] Restore database from a backup directory or .sql file`
9080
10395
  );
9081
10396
  console.log(
9082
- ` ${pc36.green("init")} [db_type] Initialize a local database & .env`
10397
+ ` ${pc39.green("init")} [db_type] Initialize a local database & .env`
9083
10398
  );
9084
10399
  console.log(
9085
- ` ${pc36.green("drop-db")} [db_type] Drop a local database`
10400
+ ` ${pc39.green("drop-db")} [db_type] Drop a local database`
9086
10401
  );
9087
10402
  console.log(`
9088
- ${pc36.bold("Options:")}`);
10403
+ ${pc39.bold("Options:")}`);
9089
10404
  console.log(` -v, --version Show drixio version`);
9090
10405
  console.log(` --help Show this help message`);
9091
10406
  console.log(` --json Output results as JSON`);