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/README.md +1 -0
- package/dist/cli.js +2150 -835
- package/dist/studio/assets/index-DOTfHOMW.js +3244 -0
- package/dist/studio/assets/index-DPNeFArq.css +1 -0
- package/dist/studio/index.html +38 -34
- package/package.json +4 -2
- package/dist/studio/assets/index-Ceja0TFO.js +0 -2288
- package/dist/studio/assets/index-DNe5A4CC.css +0 -1
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
|
-
|
|
35
|
-
if (
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
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
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
474
|
-
|
|
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 =
|
|
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 =
|
|
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, [
|
|
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 =
|
|
1248
|
+
WHERE tc.table_name = $1 AND tc.table_schema = $2;
|
|
708
1249
|
`;
|
|
709
|
-
const checkRes = await this.getPool().query(checkQuery, [
|
|
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:
|
|
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:
|
|
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
|
-
|
|
756
|
-
pg_index ix
|
|
757
|
-
|
|
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.
|
|
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
|
|
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, [
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
1293
|
-
${
|
|
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
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
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
|
-
|
|
1302
|
-
|
|
1690
|
+
async executeSql(sql) {
|
|
1691
|
+
const pool = await this.getPool();
|
|
1692
|
+
await pool.query(sql);
|
|
1303
1693
|
}
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
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
|
-
|
|
1348
|
-
})
|
|
1349
|
-
|
|
1350
|
-
|
|
1714
|
+
await connection.commit();
|
|
1715
|
+
} catch (e) {
|
|
1716
|
+
await connection.rollback();
|
|
1717
|
+
throw e;
|
|
1718
|
+
} finally {
|
|
1719
|
+
connection.release();
|
|
1351
1720
|
}
|
|
1352
|
-
|
|
1353
|
-
|
|
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 ${
|
|
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
|
|
1695
|
-
|
|
1696
|
-
|
|
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
|
|
2172
|
+
const formatted = formatSqlDefaultValue(edits.defaultValue);
|
|
2173
|
+
const defStr = formatted !== null ? `DEFAULT ${formatted}` : "";
|
|
1702
2174
|
sqls.push(
|
|
1703
|
-
`ALTER TABLE ${quotedTable} MODIFY COLUMN ${quotedCol} ${
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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:
|
|
4530
|
-
await
|
|
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
|
|
4604
|
-
import
|
|
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 =
|
|
5462
|
+
const absolutePath = path15.resolve(process.cwd(), filePath.trim());
|
|
4626
5463
|
let fileContent = "";
|
|
4627
5464
|
try {
|
|
4628
|
-
fileContent = await
|
|
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:
|
|
5164
|
-
await
|
|
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
|
|
5169
|
-
"src/tui/wizards/
|
|
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
|
|
5185
|
-
import
|
|
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
|
|
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
|
|
5560
|
-
|
|
5561
|
-
|
|
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 (
|
|
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
|
-
|
|
5622
|
-
|
|
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
|
-
|
|
5798
|
-
|
|
5799
|
-
|
|
5800
|
-
|
|
5801
|
-
|
|
5802
|
-
|
|
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(
|
|
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
|
|
5887
|
-
|
|
5888
|
-
|
|
5889
|
-
|
|
5890
|
-
|
|
5891
|
-
|
|
5892
|
-
|
|
5893
|
-
|
|
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
|
|
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 =
|
|
6290
|
-
if (!
|
|
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 =
|
|
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 "${
|
|
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 (!
|
|
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 =
|
|
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
|
-
|
|
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
|
|
6439
|
-
import
|
|
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 ?
|
|
6446
|
-
const
|
|
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:
|
|
7761
|
+
serveStatic({ root: path19.relative(process.cwd(), studioDistPath) })
|
|
6454
7762
|
);
|
|
6455
7763
|
app.get("*", async (c) => {
|
|
6456
|
-
const indexPath =
|
|
7764
|
+
const indexPath = path19.join(studioDistPath, "index.html");
|
|
6457
7765
|
try {
|
|
6458
|
-
const html = await
|
|
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
|
-
|
|
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
|
-
|
|
7783
|
+
pc38.yellow(
|
|
6476
7784
|
`\u26A0\uFE0F No database detected. Studio started in standalone mode.`
|
|
6477
7785
|
)
|
|
6478
7786
|
);
|
|
6479
7787
|
console.log(
|
|
6480
|
-
|
|
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(
|
|
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 =
|
|
6516
|
-
__dirname =
|
|
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
|
|
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:
|
|
6607
|
-
sql = await
|
|
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
|
|
6641
|
-
import
|
|
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:
|
|
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
|
|
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
|
|
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 =
|
|
6679
|
-
await
|
|
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 =
|
|
6699
|
-
await
|
|
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
|
|
6719
|
-
import
|
|
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:
|
|
8038
|
+
const { input: input11, select: select16 } = await import("@inquirer/prompts");
|
|
6731
8039
|
if (!filePath) {
|
|
6732
|
-
filePath = await
|
|
8040
|
+
filePath = await input11({
|
|
6733
8041
|
message: "Enter the path to your CSV or JSON file:"
|
|
6734
8042
|
});
|
|
6735
8043
|
}
|
|
6736
|
-
const resolvedPath =
|
|
8044
|
+
const resolvedPath = path7.resolve(process.cwd(), filePath);
|
|
6737
8045
|
try {
|
|
6738
|
-
await
|
|
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
|
|
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
|
|
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:
|
|
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
|
|
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
|
|
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:
|
|
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
|
|
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
|
|
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
|
|
6925
|
-
import
|
|
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:
|
|
8242
|
+
const { input: input11 } = await import("@inquirer/prompts");
|
|
6935
8243
|
if (!filePath) {
|
|
6936
|
-
filePath = await
|
|
8244
|
+
filePath = await input11({ message: "Enter the path to your .sql file:" });
|
|
6937
8245
|
}
|
|
6938
|
-
const resolvedPath =
|
|
8246
|
+
const resolvedPath = path8.resolve(process.cwd(), filePath);
|
|
6939
8247
|
try {
|
|
6940
|
-
await
|
|
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
|
|
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
|
|
7025
|
-
import
|
|
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:
|
|
8342
|
+
const { input: input11, select: select16, confirm: confirm8 } = await import("@inquirer/prompts");
|
|
7035
8343
|
if (!targetPath) {
|
|
7036
8344
|
try {
|
|
7037
|
-
const
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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 =
|
|
8377
|
+
const resolvedPath = path9.resolve(process.cwd(), targetPath.trim());
|
|
7086
8378
|
try {
|
|
7087
|
-
await
|
|
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
|
|
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
|
|
7151
|
-
import
|
|
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 =
|
|
7190
|
-
await
|
|
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
|
|
7212
|
-
import
|
|
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 =
|
|
7237
|
-
await
|
|
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
|
|
7253
|
-
import
|
|
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 =
|
|
8601
|
+
const outPath = path12.resolve(
|
|
7310
8602
|
process.cwd(),
|
|
7311
8603
|
options.out || defaultFileName
|
|
7312
8604
|
);
|
|
7313
|
-
await
|
|
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:
|
|
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
|
|
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
|
|
8675
|
+
const host = await input11({
|
|
7384
8676
|
message: "Server Host:",
|
|
7385
8677
|
default: "localhost"
|
|
7386
8678
|
});
|
|
7387
|
-
const port = await
|
|
8679
|
+
const port = await input11({
|
|
7388
8680
|
message: "Server Port:",
|
|
7389
8681
|
default: dialect === "mysql" ? "3306" : "5432"
|
|
7390
8682
|
});
|
|
7391
|
-
const user = await
|
|
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
|
|
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:
|
|
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
|
|
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
|
|
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
|
|
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
|
|
8830
|
+
const host = await input11({
|
|
7539
8831
|
message: "Server Host:",
|
|
7540
8832
|
default: "localhost"
|
|
7541
8833
|
});
|
|
7542
|
-
const port = await
|
|
8834
|
+
const port = await input11({
|
|
7543
8835
|
message: "Server Port:",
|
|
7544
8836
|
default: dialect === "mysql" ? "3306" : "5432"
|
|
7545
8837
|
});
|
|
7546
|
-
const user = await
|
|
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
|
|
8846
|
+
dbName = await input11({
|
|
7555
8847
|
message: "Which Database Name do you want to DROP?"
|
|
7556
8848
|
});
|
|
7557
8849
|
}
|
|
7558
|
-
const sure = await
|
|
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:
|
|
7680
|
-
tableName = await
|
|
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
|
|
9061
|
+
import fs13 from "fs/promises";
|
|
7770
9062
|
import { existsSync as existsSync2 } from "fs";
|
|
7771
|
-
import
|
|
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
|
-
|
|
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 =
|
|
7801
|
-
await
|
|
7802
|
-
await
|
|
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(
|
|
9111
|
+
if (existsSync2(path13.resolve(process.cwd(), "schema.json"))) {
|
|
7820
9112
|
targetArg = "schema.json";
|
|
7821
|
-
} else if (existsSync2(
|
|
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 =
|
|
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 =
|
|
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
|
|
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 (${
|
|
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 =
|
|
7920
|
-
await
|
|
7921
|
-
await
|
|
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:
|
|
7938
|
-
proceed = await
|
|
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:
|
|
8111
|
-
const selectedId = await
|
|
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:
|
|
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
|
|
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
|
|
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
|
|
8306
|
-
import
|
|
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 =
|
|
9718
|
+
const absolutePath = path14.resolve(process.cwd(), filePath.trim());
|
|
8427
9719
|
let fileContent = "";
|
|
8428
9720
|
try {
|
|
8429
|
-
fileContent = await
|
|
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:
|
|
8547
|
-
await
|
|
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:
|
|
8617
|
-
const searchInput = await
|
|
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
|
|
8642
|
-
const
|
|
8643
|
-
const exportDir =
|
|
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
|
|
8648
|
-
const fp =
|
|
9939
|
+
await fs19.mkdir(exportDir, { recursive: true });
|
|
9940
|
+
const fp = path20.join(
|
|
8649
9941
|
exportDir,
|
|
8650
9942
|
`${selectedTable}.${format}`
|
|
8651
9943
|
);
|
|
8652
|
-
await
|
|
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:
|
|
8661
|
-
await
|
|
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:
|
|
8724
|
-
await
|
|
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:
|
|
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
|
|
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
|
-
|
|
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(
|
|
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:
|
|
8968
|
-
await
|
|
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${
|
|
10330
|
+
console.log(`drixio v${getDrixioVersion()}`);
|
|
9016
10331
|
process.exit(0);
|
|
9017
10332
|
}
|
|
9018
10333
|
if (values.help) {
|
|
9019
|
-
console.log(
|
|
10334
|
+
console.log(pc39.cyan(`
|
|
9020
10335
|
Drixio - Modern Database Manager
|
|
9021
10336
|
`));
|
|
9022
|
-
console.log(`${
|
|
10337
|
+
console.log(`${pc39.bold("Usage:")} npx drixio [command] [options]
|
|
9023
10338
|
`);
|
|
9024
|
-
console.log(`${
|
|
10339
|
+
console.log(`${pc39.bold("Tiers & Interfaces:")}`);
|
|
9025
10340
|
console.log(
|
|
9026
|
-
` Interactive TUI: ${
|
|
10341
|
+
` Interactive TUI: ${pc39.green("npx drixio")} (Terminal UI with beginner wizards)`
|
|
9027
10342
|
);
|
|
9028
10343
|
console.log(
|
|
9029
|
-
` Web Studio: ${
|
|
10344
|
+
` Web Studio: ${pc39.green("npx drixio studio")} (Browser UI for bulk inspection)`
|
|
9030
10345
|
);
|
|
9031
10346
|
console.log(`
|
|
9032
|
-
${
|
|
10347
|
+
${pc39.bold("Quick Commands (Headless / Pro):")}`);
|
|
9033
10348
|
console.log(
|
|
9034
|
-
` ${
|
|
10349
|
+
` ${pc39.green("tables")} List all database tables and row counts (alias: ls)`
|
|
9035
10350
|
);
|
|
9036
10351
|
console.log(
|
|
9037
|
-
` ${
|
|
10352
|
+
` ${pc39.green("describe")} [table] Inspect columns, types, PKs, and indexes (alias: desc)`
|
|
9038
10353
|
);
|
|
9039
10354
|
console.log(
|
|
9040
|
-
` ${
|
|
10355
|
+
` ${pc39.green("query")} "<sql>" Run a quick SQL query`
|
|
9041
10356
|
);
|
|
9042
10357
|
console.log(
|
|
9043
|
-
` ${
|
|
10358
|
+
` ${pc39.green("exec")} <file.sql> Execute a SQL script file`
|
|
9044
10359
|
);
|
|
9045
10360
|
console.log(
|
|
9046
|
-
` ${
|
|
10361
|
+
` ${pc39.green("export")} [table] Export table(s) to CSV/JSON`
|
|
9047
10362
|
);
|
|
9048
10363
|
console.log(
|
|
9049
|
-
` ${
|
|
10364
|
+
` ${pc39.green("import")} [file] Import JSON/CSV into a table`
|
|
9050
10365
|
);
|
|
9051
10366
|
console.log(
|
|
9052
|
-
` ${
|
|
10367
|
+
` ${pc39.green("seed")} [table] [count] Generate realistic fake data for a table`
|
|
9053
10368
|
);
|
|
9054
10369
|
console.log(
|
|
9055
|
-
` ${
|
|
10370
|
+
` ${pc39.green("truncate")} [table] Empty all data in a table and reset sequence`
|
|
9056
10371
|
);
|
|
9057
10372
|
console.log(
|
|
9058
|
-
` ${
|
|
10373
|
+
` ${pc39.green("diagram")} Generate a Mermaid ER diagram`
|
|
9059
10374
|
);
|
|
9060
10375
|
console.log(
|
|
9061
|
-
` ${
|
|
10376
|
+
` ${pc39.green("generate-types")} Generate TypeScript interfaces`
|
|
9062
10377
|
);
|
|
9063
10378
|
console.log(
|
|
9064
|
-
` ${
|
|
10379
|
+
` ${pc39.green("generate-orm")} [target] Generate Prisma or Drizzle ORM schema`
|
|
9065
10380
|
);
|
|
9066
10381
|
console.log(
|
|
9067
|
-
` ${
|
|
10382
|
+
` ${pc39.green("diff")} [target] Compare schemas & generate migration SQL`
|
|
9068
10383
|
);
|
|
9069
10384
|
console.log(
|
|
9070
|
-
` ${
|
|
10385
|
+
` ${pc39.green("snippets")} List saved SQL snippets & templates (alias: snip)`
|
|
9071
10386
|
);
|
|
9072
10387
|
console.log(
|
|
9073
|
-
` ${
|
|
10388
|
+
` ${pc39.green("run")} [snippet] Execute a saved snippet or parametric query`
|
|
9074
10389
|
);
|
|
9075
10390
|
console.log(
|
|
9076
|
-
` ${
|
|
10391
|
+
` ${pc39.green("backup")} Backup the entire database`
|
|
9077
10392
|
);
|
|
9078
10393
|
console.log(
|
|
9079
|
-
` ${
|
|
10394
|
+
` ${pc39.green("restore")} [dir|file] Restore database from a backup directory or .sql file`
|
|
9080
10395
|
);
|
|
9081
10396
|
console.log(
|
|
9082
|
-
` ${
|
|
10397
|
+
` ${pc39.green("init")} [db_type] Initialize a local database & .env`
|
|
9083
10398
|
);
|
|
9084
10399
|
console.log(
|
|
9085
|
-
` ${
|
|
10400
|
+
` ${pc39.green("drop-db")} [db_type] Drop a local database`
|
|
9086
10401
|
);
|
|
9087
10402
|
console.log(`
|
|
9088
|
-
${
|
|
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`);
|