@askdb/studio 0.2.0-beta.24 → 0.2.0-beta.25
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 +18 -0
- package/dist/client/assets/index-Bj6qGfT6.css +2 -0
- package/dist/client/assets/index-DYBXDl3K.js +69 -0
- package/dist/client/index.html +2 -2
- package/dist/execute-registry.d.ts +34 -0
- package/dist/execute-registry.d.ts.map +1 -0
- package/dist/execute-registry.js +240 -0
- package/dist/execute-registry.js.map +1 -0
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +168 -39
- package/dist/server.js.map +1 -1
- package/dist/shared/api.d.ts +25 -0
- package/dist/shared/api.d.ts.map +1 -1
- package/package.json +46 -20
- package/dist/client/assets/index-DY6BxO-7.css +0 -2
- package/dist/client/assets/index-_lnkmWaK.js +0 -68
package/dist/client/index.html
CHANGED
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
<title>AskDB Studio</title>
|
|
7
7
|
<link rel="icon" type="image/png" media="(prefers-color-scheme: light)" href="/assets/brand/light-icon.png" />
|
|
8
8
|
<link rel="icon" type="image/png" media="(prefers-color-scheme: dark)" href="/assets/brand/dark-icon.png" />
|
|
9
|
-
<script type="module" crossorigin src="/assets/index-
|
|
10
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
9
|
+
<script type="module" crossorigin src="/assets/index-DYBXDl3K.js"></script>
|
|
10
|
+
<link rel="stylesheet" crossorigin href="/assets/index-Bj6qGfT6.css">
|
|
11
11
|
</head>
|
|
12
12
|
<body>
|
|
13
13
|
<div id="root"></div>
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Studio execute-driver registry.
|
|
3
|
+
*
|
|
4
|
+
* Each entry describes a live execute provider — how to dynamically import the
|
|
5
|
+
* driver package, what connection shape it expects, and how to normalize its
|
|
6
|
+
* results into the shared `ExecuteResponse` DTO.
|
|
7
|
+
*
|
|
8
|
+
* Drivers are optional peers. The registry never imports a driver at module
|
|
9
|
+
* load time; every import lives inside an `executeXxx()` function so a missing
|
|
10
|
+
* package only fails when the user actually tries to execute a query.
|
|
11
|
+
*/
|
|
12
|
+
import type { ExecuteResponse } from "./shared/api.js";
|
|
13
|
+
export type StudioExecuteProvider = "postgres" | "mysql" | "sqlite" | "sqlserver";
|
|
14
|
+
export type StudioExecuteInput = {
|
|
15
|
+
connectionString?: string;
|
|
16
|
+
file?: string;
|
|
17
|
+
sql: string;
|
|
18
|
+
params: unknown[];
|
|
19
|
+
};
|
|
20
|
+
export type StudioDriverDefinition = {
|
|
21
|
+
provider: StudioExecuteProvider;
|
|
22
|
+
label: string;
|
|
23
|
+
packageName: "pg" | "mysql2" | "better-sqlite3" | "mssql";
|
|
24
|
+
installCommand: string;
|
|
25
|
+
execute(input: StudioExecuteInput): Promise<ExecuteResponse>;
|
|
26
|
+
};
|
|
27
|
+
export declare const EXECUTE_DRIVER_REGISTRY: Record<StudioExecuteProvider, StudioDriverDefinition>;
|
|
28
|
+
/**
|
|
29
|
+
* Check whether a driver package is importable at runtime.
|
|
30
|
+
* Returns `true` when the import succeeds (even if the value is empty),
|
|
31
|
+
* `false` when it throws (module not found).
|
|
32
|
+
*/
|
|
33
|
+
export declare function isDriverInstalled(packageName: string): Promise<boolean>;
|
|
34
|
+
//# sourceMappingURL=execute-registry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"execute-registry.d.ts","sourceRoot":"","sources":["../src/execute-registry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAMvD,MAAM,MAAM,qBAAqB,GAAG,UAAU,GAAG,OAAO,GAAG,QAAQ,GAAG,WAAW,CAAC;AAElF,MAAM,MAAM,kBAAkB,GAAG;IAC/B,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,OAAO,EAAE,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,sBAAsB,GAAG;IACnC,QAAQ,EAAE,qBAAqB,CAAC;IAChC,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,IAAI,GAAG,QAAQ,GAAG,gBAAgB,GAAG,OAAO,CAAC;IAC1D,cAAc,EAAE,MAAM,CAAC;IACvB,OAAO,CAAC,KAAK,EAAE,kBAAkB,GAAG,OAAO,CAAC,eAAe,CAAC,CAAC;CAC9D,CAAC;AA6PF,eAAO,MAAM,uBAAuB,EAAE,MAAM,CAAC,qBAAqB,EAAE,sBAAsB,CA6BzF,CAAC;AAEF;;;;GAIG;AACH,wBAAsB,iBAAiB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAO7E"}
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Studio execute-driver registry.
|
|
3
|
+
*
|
|
4
|
+
* Each entry describes a live execute provider — how to dynamically import the
|
|
5
|
+
* driver package, what connection shape it expects, and how to normalize its
|
|
6
|
+
* results into the shared `ExecuteResponse` DTO.
|
|
7
|
+
*
|
|
8
|
+
* Drivers are optional peers. The registry never imports a driver at module
|
|
9
|
+
* load time; every import lives inside an `executeXxx()` function so a missing
|
|
10
|
+
* package only fails when the user actually tries to execute a query.
|
|
11
|
+
*/
|
|
12
|
+
// ---------------------------------------------------------------------------
|
|
13
|
+
// Normalization helpers
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
const MAX_ROWS = 500;
|
|
16
|
+
function buildOkResponse(columns, rows, rowCount, durationMs) {
|
|
17
|
+
const truncated = rows.length > MAX_ROWS;
|
|
18
|
+
return {
|
|
19
|
+
ok: true,
|
|
20
|
+
columns,
|
|
21
|
+
rows: rows.slice(0, MAX_ROWS),
|
|
22
|
+
rowCount,
|
|
23
|
+
durationMs,
|
|
24
|
+
truncated,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
async function executePostgres(input) {
|
|
28
|
+
const { connectionString, sql, params } = input;
|
|
29
|
+
if (!connectionString) {
|
|
30
|
+
return { ok: false, error: "No connection URL configured for Postgres execute. Set ASKDB_STUDIO_DATABASE_URL or introspection.providerConfig.postgres.databaseUrl." };
|
|
31
|
+
}
|
|
32
|
+
let pgMod;
|
|
33
|
+
try {
|
|
34
|
+
const mod = await import("pg");
|
|
35
|
+
pgMod = (mod.default ?? mod);
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
return { ok: false, error: "The `pg` package is not installed. Install it with `pnpm add pg`." };
|
|
39
|
+
}
|
|
40
|
+
const client = new pgMod.Client({ connectionString });
|
|
41
|
+
try {
|
|
42
|
+
await client.connect();
|
|
43
|
+
}
|
|
44
|
+
catch (err) {
|
|
45
|
+
return { ok: false, error: err instanceof Error ? err.message : String(err) };
|
|
46
|
+
}
|
|
47
|
+
const startMs = Date.now();
|
|
48
|
+
try {
|
|
49
|
+
await client.query("BEGIN READ ONLY");
|
|
50
|
+
const result = await client.query({ text: sql, values: params });
|
|
51
|
+
await client.query("COMMIT");
|
|
52
|
+
const durationMs = Date.now() - startMs;
|
|
53
|
+
const columns = result.fields.map((f) => f.name);
|
|
54
|
+
const rows = result.rows.map((r) => columns.map((c) => r[c]));
|
|
55
|
+
return buildOkResponse(columns, rows, result.rowCount ?? rows.length, durationMs);
|
|
56
|
+
}
|
|
57
|
+
catch (err) {
|
|
58
|
+
await client.query("ROLLBACK").catch(() => { });
|
|
59
|
+
return { ok: false, error: err instanceof Error ? err.message : String(err) };
|
|
60
|
+
}
|
|
61
|
+
finally {
|
|
62
|
+
await client.end().catch(() => { });
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
async function executeMySQL(input) {
|
|
66
|
+
const { connectionString, sql, params } = input;
|
|
67
|
+
if (!connectionString) {
|
|
68
|
+
return { ok: false, error: "No connection URL configured for MySQL execute. Set ASKDB_STUDIO_DATABASE_URL or introspection.providerConfig.mysql.databaseUrl." };
|
|
69
|
+
}
|
|
70
|
+
let mysql2Mod;
|
|
71
|
+
try {
|
|
72
|
+
const mod = await import("mysql2/promise");
|
|
73
|
+
mysql2Mod = (mod.default ?? mod);
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
return { ok: false, error: "The `mysql2` package is not installed. Install it with `pnpm add mysql2`." };
|
|
77
|
+
}
|
|
78
|
+
let conn;
|
|
79
|
+
try {
|
|
80
|
+
conn = await mysql2Mod.createConnection({ uri: connectionString });
|
|
81
|
+
}
|
|
82
|
+
catch (err) {
|
|
83
|
+
return { ok: false, error: err instanceof Error ? err.message : String(err) };
|
|
84
|
+
}
|
|
85
|
+
const startMs = Date.now();
|
|
86
|
+
try {
|
|
87
|
+
await conn.query("SET SESSION TRANSACTION READ ONLY");
|
|
88
|
+
await conn.query("START TRANSACTION");
|
|
89
|
+
const [rowsRaw, fields] = await conn.execute(sql, params);
|
|
90
|
+
await conn.query("ROLLBACK");
|
|
91
|
+
const durationMs = Date.now() - startMs;
|
|
92
|
+
const columns = fields.map((f) => f.name);
|
|
93
|
+
const rows = rowsRaw.map((r) => columns.map((c) => r[c]));
|
|
94
|
+
return buildOkResponse(columns, rows, rows.length, durationMs);
|
|
95
|
+
}
|
|
96
|
+
catch (err) {
|
|
97
|
+
await conn.query("ROLLBACK").catch(() => { });
|
|
98
|
+
return { ok: false, error: err instanceof Error ? err.message : String(err) };
|
|
99
|
+
}
|
|
100
|
+
finally {
|
|
101
|
+
await conn.end().catch(() => { });
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
async function executeSQLite(input) {
|
|
105
|
+
const { file, sql, params } = input;
|
|
106
|
+
if (!file) {
|
|
107
|
+
return { ok: false, error: "No SQLite file path configured for execute. Set ASKDB_STUDIO_SQLITE_FILE or introspection.providerConfig.sqlite.file." };
|
|
108
|
+
}
|
|
109
|
+
let Sqlite3;
|
|
110
|
+
try {
|
|
111
|
+
const mod = await import("better-sqlite3");
|
|
112
|
+
Sqlite3 = mod.default;
|
|
113
|
+
}
|
|
114
|
+
catch {
|
|
115
|
+
return { ok: false, error: "The `better-sqlite3` package is not installed. Install it with `pnpm add better-sqlite3`." };
|
|
116
|
+
}
|
|
117
|
+
const startMs = Date.now();
|
|
118
|
+
const db = new Sqlite3(file, { readonly: true, fileMustExist: true });
|
|
119
|
+
try {
|
|
120
|
+
const stmt = db.prepare(sql);
|
|
121
|
+
const columns = stmt.columns().map((c) => c.name);
|
|
122
|
+
const rowsRaw = stmt.all(...params);
|
|
123
|
+
const durationMs = Date.now() - startMs;
|
|
124
|
+
const rows = rowsRaw.map((r) => columns.map((c) => r[c]));
|
|
125
|
+
return buildOkResponse(columns, rows, rows.length, durationMs);
|
|
126
|
+
}
|
|
127
|
+
catch (err) {
|
|
128
|
+
return { ok: false, error: err instanceof Error ? err.message : String(err) };
|
|
129
|
+
}
|
|
130
|
+
finally {
|
|
131
|
+
db.close();
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Rewrite `$1`, `$2`, ... or bare `?` positional params to MSSQL `@p0`, `@p1`, ...
|
|
136
|
+
* and return an object of named bindings.
|
|
137
|
+
*/
|
|
138
|
+
function rewriteSqlServerParams(sql, params) {
|
|
139
|
+
if (params.length === 0)
|
|
140
|
+
return { sql, bindings: {} };
|
|
141
|
+
const bindings = {};
|
|
142
|
+
params.forEach((value, idx) => {
|
|
143
|
+
bindings[`p${idx}`] = value;
|
|
144
|
+
});
|
|
145
|
+
// Replace $N placeholders (1-indexed) first, then bare ? placeholders.
|
|
146
|
+
let idx = 0;
|
|
147
|
+
const rewritten = sql
|
|
148
|
+
.replace(/\$\d+/g, () => `@p${idx++}`)
|
|
149
|
+
.replace(/\?/g, () => `@p${idx++}`);
|
|
150
|
+
return { sql: rewritten, bindings };
|
|
151
|
+
}
|
|
152
|
+
async function executeSQLServer(input) {
|
|
153
|
+
const { connectionString, sql, params } = input;
|
|
154
|
+
if (!connectionString) {
|
|
155
|
+
return { ok: false, error: "No connection URL configured for SQL Server execute. Set ASKDB_STUDIO_DATABASE_URL or introspection.providerConfig.sqlserver.databaseUrl." };
|
|
156
|
+
}
|
|
157
|
+
let mssqlMod;
|
|
158
|
+
try {
|
|
159
|
+
const mod = await import("mssql");
|
|
160
|
+
mssqlMod = (mod.default ?? mod);
|
|
161
|
+
}
|
|
162
|
+
catch {
|
|
163
|
+
return { ok: false, error: "The `mssql` package is not installed. Install it with `pnpm add mssql`." };
|
|
164
|
+
}
|
|
165
|
+
let pool;
|
|
166
|
+
try {
|
|
167
|
+
pool = await new mssqlMod.ConnectionPool(connectionString).connect();
|
|
168
|
+
}
|
|
169
|
+
catch (err) {
|
|
170
|
+
return { ok: false, error: err instanceof Error ? err.message : String(err) };
|
|
171
|
+
}
|
|
172
|
+
const startMs = Date.now();
|
|
173
|
+
try {
|
|
174
|
+
const { sql: rewrittenSql, bindings } = rewriteSqlServerParams(sql, params);
|
|
175
|
+
const req = pool.request();
|
|
176
|
+
for (const [name, value] of Object.entries(bindings)) {
|
|
177
|
+
req.input(name, value);
|
|
178
|
+
}
|
|
179
|
+
const result = await req.query(rewrittenSql);
|
|
180
|
+
const durationMs = Date.now() - startMs;
|
|
181
|
+
const rowsRaw = result.recordset ?? [];
|
|
182
|
+
const columns = rowsRaw.length > 0 ? Object.keys(rowsRaw[0]) : [];
|
|
183
|
+
const rows = rowsRaw.map((r) => columns.map((c) => r[c]));
|
|
184
|
+
return buildOkResponse(columns, rows, rows.length, durationMs);
|
|
185
|
+
}
|
|
186
|
+
catch (err) {
|
|
187
|
+
return { ok: false, error: err instanceof Error ? err.message : String(err) };
|
|
188
|
+
}
|
|
189
|
+
finally {
|
|
190
|
+
await pool.close();
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
// ---------------------------------------------------------------------------
|
|
194
|
+
// Registry
|
|
195
|
+
// ---------------------------------------------------------------------------
|
|
196
|
+
export const EXECUTE_DRIVER_REGISTRY = {
|
|
197
|
+
postgres: {
|
|
198
|
+
provider: "postgres",
|
|
199
|
+
label: "Postgres",
|
|
200
|
+
packageName: "pg",
|
|
201
|
+
installCommand: "pnpm add pg",
|
|
202
|
+
execute: executePostgres,
|
|
203
|
+
},
|
|
204
|
+
mysql: {
|
|
205
|
+
provider: "mysql",
|
|
206
|
+
label: "MySQL",
|
|
207
|
+
packageName: "mysql2",
|
|
208
|
+
installCommand: "pnpm add mysql2",
|
|
209
|
+
execute: executeMySQL,
|
|
210
|
+
},
|
|
211
|
+
sqlite: {
|
|
212
|
+
provider: "sqlite",
|
|
213
|
+
label: "SQLite",
|
|
214
|
+
packageName: "better-sqlite3",
|
|
215
|
+
installCommand: "pnpm add better-sqlite3",
|
|
216
|
+
execute: executeSQLite,
|
|
217
|
+
},
|
|
218
|
+
sqlserver: {
|
|
219
|
+
provider: "sqlserver",
|
|
220
|
+
label: "SQL Server",
|
|
221
|
+
packageName: "mssql",
|
|
222
|
+
installCommand: "pnpm add mssql",
|
|
223
|
+
execute: executeSQLServer,
|
|
224
|
+
},
|
|
225
|
+
};
|
|
226
|
+
/**
|
|
227
|
+
* Check whether a driver package is importable at runtime.
|
|
228
|
+
* Returns `true` when the import succeeds (even if the value is empty),
|
|
229
|
+
* `false` when it throws (module not found).
|
|
230
|
+
*/
|
|
231
|
+
export async function isDriverInstalled(packageName) {
|
|
232
|
+
try {
|
|
233
|
+
await import(packageName);
|
|
234
|
+
return true;
|
|
235
|
+
}
|
|
236
|
+
catch {
|
|
237
|
+
return false;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
//# sourceMappingURL=execute-registry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"execute-registry.js","sourceRoot":"","sources":["../src/execute-registry.ts"],"names":[],"mappings":"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"}
|
package/dist/server.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,YAAY,EAA6C,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,YAAY,EAA6C,MAAM,WAAW,CAAC;AA+BpF,OAAO,EAOL,mBAAmB,EAOpB,MAAM,YAAY,CAAC;AACpB,OAAO,EAaL,KAAK,SAAS,EACf,MAAM,eAAe,CAAC;AACvB,OAAO,KAAK,EAYV,kBAAkB,EAGnB,MAAM,iBAAiB,CAAC;AAQzB,MAAM,MAAM,aAAa,GAAG;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,MAAM,MAAM,YAAY,GAAG,UAAU,CAAC,OAAO,YAAY,CAAC,CAAC;AAyD3D,wBAAgB,qCAAqC,CACnD,OAAO,EAAE,OAAO,mBAAmB,GAAG,SAAS,GAC9C,IAAI,CAEN;AAED,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,aAAa,GAAG,YAAY,CAiHvE;AAED,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,SAAS,GAAG,kBAAkB,CAyC3E"}
|
package/dist/server.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
2
|
import { existsSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
3
3
|
import { createServer } from "node:http";
|
|
4
|
-
import { extname, relative, join, resolve } from "node:path";
|
|
4
|
+
import { extname, relative, join, resolve, dirname } from "node:path";
|
|
5
|
+
import { spawn } from "node:child_process";
|
|
5
6
|
import { fileURLToPath } from "node:url";
|
|
6
7
|
import { generateText as defaultGenerateText } from "ai";
|
|
7
8
|
import { getAskDbRuntimeConfig } from "@askdb/config";
|
|
@@ -13,6 +14,7 @@ import { openaiProvider } from "@askdb/ai-openai";
|
|
|
13
14
|
import { ask, isBuiltInDialectId, loadSchema, suggestEnrichment, tenantScopeSchema, writeTenantPolicyMarkdown, tenantPolicyFrontmatterSchema, } from "@askdb/core";
|
|
14
15
|
import { buildSchemaIndex, chunkContentHash, chunkSchema, createAiSdkEmbedder, createFileStore, createMemoryStore, createPgvectorStore, createRetriever, loadChunkerSourcesFromDir, } from "@askdb/rag";
|
|
15
16
|
import { buildDefaultTableBody, buildFrontmatter, buildSuggestionContext, buildSuggestionTarget, buildTableDraft, loadWorkspace, replaceH2Section, replaceTableDescription, saveConcepts, saveTable, } from "@askdb/enrich";
|
|
17
|
+
import { EXECUTE_DRIVER_REGISTRY, isDriverInstalled } from "./execute-registry.js";
|
|
16
18
|
const ai = createAiRegistry([openaiProvider, azureProvider, googleProvider, anthropicProvider]);
|
|
17
19
|
const CLIENT_DIR = fileURLToPath(new URL("./client/", import.meta.url));
|
|
18
20
|
const STUDIO_RAG_MOCK_DIMENSIONS = 64;
|
|
@@ -105,6 +107,18 @@ export function createStudioServer(options) {
|
|
|
105
107
|
const result = deletePlaygroundHistoryEntry(state.schemaDir, id);
|
|
106
108
|
return writeJson(res, 200, result);
|
|
107
109
|
}
|
|
110
|
+
if (req.method === "GET" && url.pathname === "/api/execute/status") {
|
|
111
|
+
const result = await getExecuteStatus();
|
|
112
|
+
return writeJson(res, 200, result);
|
|
113
|
+
}
|
|
114
|
+
if (req.method === "POST" && url.pathname === "/api/execute/install-driver") {
|
|
115
|
+
if (!isLoopbackRequest(req)) {
|
|
116
|
+
return writeJson(res, 403, { error: { message: "Driver install is only available from loopback clients." } });
|
|
117
|
+
}
|
|
118
|
+
const body = await readJson(req);
|
|
119
|
+
const result = await installExecuteDriver(body, state.schemaDir);
|
|
120
|
+
return writeJson(res, 200, result);
|
|
121
|
+
}
|
|
108
122
|
if (req.method === "POST" && url.pathname === "/api/execute") {
|
|
109
123
|
const body = await readJson(req);
|
|
110
124
|
const result = await executeQuery(body);
|
|
@@ -865,56 +879,171 @@ function parsePlaygroundHistoryEntry(body) {
|
|
|
865
879
|
return body;
|
|
866
880
|
}
|
|
867
881
|
// ---------------------------------------------------------------------------
|
|
868
|
-
// Execute endpoint
|
|
882
|
+
// Execute endpoint helpers
|
|
869
883
|
// ---------------------------------------------------------------------------
|
|
870
|
-
async function
|
|
871
|
-
const
|
|
872
|
-
|
|
873
|
-
|
|
884
|
+
async function getExecuteStatus() {
|
|
885
|
+
const rt = getAskDbRuntimeConfig();
|
|
886
|
+
const { provider, databaseUrl, file } = rt.studio.execute;
|
|
887
|
+
const def = EXECUTE_DRIVER_REGISTRY[provider];
|
|
888
|
+
const installed = await isDriverInstalled(def.packageName);
|
|
889
|
+
const connectionKind = provider === "sqlite" ? "file" : "url";
|
|
890
|
+
const configured = connectionKind === "file" ? Boolean(file) : Boolean(databaseUrl);
|
|
891
|
+
return {
|
|
892
|
+
provider,
|
|
893
|
+
label: def.label,
|
|
894
|
+
configured,
|
|
895
|
+
connectionKind,
|
|
896
|
+
packageName: def.packageName,
|
|
897
|
+
installed,
|
|
898
|
+
installCommand: def.installCommand,
|
|
899
|
+
canInstallFromStudio: true,
|
|
900
|
+
manualInstallReason: null,
|
|
901
|
+
};
|
|
902
|
+
}
|
|
903
|
+
async function installExecuteDriver(body, schemaDir) {
|
|
904
|
+
const rt = getAskDbRuntimeConfig();
|
|
905
|
+
const configuredProvider = rt.studio.execute.provider;
|
|
906
|
+
// Accept the configured provider or an explicit allowlisted provider.
|
|
907
|
+
let provider = configuredProvider;
|
|
908
|
+
if (isRecord(body) && typeof body.provider === "string") {
|
|
909
|
+
const requested = body.provider;
|
|
910
|
+
if (!(requested in EXECUTE_DRIVER_REGISTRY)) {
|
|
911
|
+
return {
|
|
912
|
+
ok: false,
|
|
913
|
+
provider: configuredProvider,
|
|
914
|
+
packageName: EXECUTE_DRIVER_REGISTRY[configuredProvider].packageName,
|
|
915
|
+
command: [],
|
|
916
|
+
error: `Unknown provider: ${body.provider}`,
|
|
917
|
+
installed: false,
|
|
918
|
+
};
|
|
919
|
+
}
|
|
920
|
+
provider = requested;
|
|
874
921
|
}
|
|
875
|
-
|
|
876
|
-
|
|
922
|
+
const def = EXECUTE_DRIVER_REGISTRY[provider];
|
|
923
|
+
const { packageManager, command, args, manualReason } = detectPackageManager(schemaDir, def.packageName);
|
|
924
|
+
if (manualReason) {
|
|
925
|
+
return {
|
|
926
|
+
ok: false,
|
|
927
|
+
provider,
|
|
928
|
+
packageName: def.packageName,
|
|
929
|
+
command: [],
|
|
930
|
+
error: manualReason,
|
|
931
|
+
installed: false,
|
|
932
|
+
};
|
|
877
933
|
}
|
|
878
|
-
|
|
879
|
-
const
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
934
|
+
// Run the install.
|
|
935
|
+
const { stdout, stderr, code } = await spawnCommand(command, args, schemaDir);
|
|
936
|
+
const installed = await isDriverInstalled(def.packageName);
|
|
937
|
+
return {
|
|
938
|
+
ok: code === 0,
|
|
939
|
+
provider,
|
|
940
|
+
packageName: def.packageName,
|
|
941
|
+
command: [command, ...args],
|
|
942
|
+
stdout,
|
|
943
|
+
stderr,
|
|
944
|
+
error: code !== 0 ? `${packageManager} exited with code ${code}` : undefined,
|
|
945
|
+
installed,
|
|
946
|
+
};
|
|
947
|
+
}
|
|
948
|
+
function detectPackageManager(cwd, packageName) {
|
|
949
|
+
// Walk up from schemaDir to find the project root's lockfile.
|
|
950
|
+
const projectRoot = findProjectRoot(cwd);
|
|
951
|
+
if (!projectRoot) {
|
|
952
|
+
return {
|
|
953
|
+
packageManager: "",
|
|
954
|
+
command: "",
|
|
955
|
+
args: [],
|
|
956
|
+
manualReason: `Could not locate a package.json above ${cwd}. Run the install manually.`,
|
|
957
|
+
};
|
|
884
958
|
}
|
|
885
|
-
|
|
886
|
-
return {
|
|
959
|
+
if (existsSync(join(projectRoot, "pnpm-lock.yaml"))) {
|
|
960
|
+
return {
|
|
961
|
+
packageManager: "pnpm",
|
|
962
|
+
command: "pnpm",
|
|
963
|
+
args: ["add", packageName],
|
|
964
|
+
manualReason: null,
|
|
965
|
+
};
|
|
887
966
|
}
|
|
888
|
-
|
|
889
|
-
await client.connect();
|
|
890
|
-
const startMs = Date.now();
|
|
891
|
-
try {
|
|
892
|
-
await client.query("BEGIN READ ONLY");
|
|
893
|
-
const result = await client.query({ text: sql, values: params });
|
|
894
|
-
await client.query("COMMIT");
|
|
895
|
-
const durationMs = Date.now() - startMs;
|
|
896
|
-
const columns = result.fields.map((f) => f.name);
|
|
897
|
-
const rows = result.rows.map((r) => columns.map((c) => r[c]));
|
|
898
|
-
const truncated = rows.length > 500;
|
|
967
|
+
if (existsSync(join(projectRoot, "package-lock.json"))) {
|
|
899
968
|
return {
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
durationMs,
|
|
905
|
-
truncated,
|
|
969
|
+
packageManager: "npm",
|
|
970
|
+
command: "npm",
|
|
971
|
+
args: ["install", packageName],
|
|
972
|
+
manualReason: null,
|
|
906
973
|
};
|
|
907
974
|
}
|
|
908
|
-
|
|
909
|
-
await client.query("ROLLBACK").catch(() => { });
|
|
975
|
+
if (existsSync(join(projectRoot, "yarn.lock"))) {
|
|
910
976
|
return {
|
|
911
|
-
|
|
912
|
-
|
|
977
|
+
packageManager: "yarn",
|
|
978
|
+
command: "yarn",
|
|
979
|
+
args: ["add", packageName],
|
|
980
|
+
manualReason: null,
|
|
913
981
|
};
|
|
914
982
|
}
|
|
915
|
-
|
|
916
|
-
|
|
983
|
+
if (existsSync(join(projectRoot, "bun.lockb")) || existsSync(join(projectRoot, "bun.lock"))) {
|
|
984
|
+
return {
|
|
985
|
+
packageManager: "bun",
|
|
986
|
+
command: "bun",
|
|
987
|
+
args: ["add", packageName],
|
|
988
|
+
manualReason: null,
|
|
989
|
+
};
|
|
917
990
|
}
|
|
991
|
+
return {
|
|
992
|
+
packageManager: "",
|
|
993
|
+
command: "",
|
|
994
|
+
args: [],
|
|
995
|
+
manualReason: `No recognized lockfile found in ${projectRoot}. Run: pnpm add ${packageName}`,
|
|
996
|
+
};
|
|
997
|
+
}
|
|
998
|
+
function findProjectRoot(startDir) {
|
|
999
|
+
let current = resolve(startDir);
|
|
1000
|
+
for (let i = 0; i < 20; i++) {
|
|
1001
|
+
if (existsSync(join(current, "package.json")))
|
|
1002
|
+
return current;
|
|
1003
|
+
const parent = dirname(current);
|
|
1004
|
+
if (parent === current)
|
|
1005
|
+
return null;
|
|
1006
|
+
current = parent;
|
|
1007
|
+
}
|
|
1008
|
+
return null;
|
|
1009
|
+
}
|
|
1010
|
+
function spawnCommand(command, args, cwd) {
|
|
1011
|
+
return new Promise((resolve) => {
|
|
1012
|
+
const proc = spawn(command, args, { cwd, shell: false, stdio: ["ignore", "pipe", "pipe"] });
|
|
1013
|
+
const stdoutChunks = [];
|
|
1014
|
+
const stderrChunks = [];
|
|
1015
|
+
proc.stdout.on("data", (chunk) => stdoutChunks.push(chunk));
|
|
1016
|
+
proc.stderr.on("data", (chunk) => stderrChunks.push(chunk));
|
|
1017
|
+
proc.on("close", (code) => {
|
|
1018
|
+
resolve({
|
|
1019
|
+
stdout: Buffer.concat(stdoutChunks).toString("utf8").slice(0, 4096),
|
|
1020
|
+
stderr: Buffer.concat(stderrChunks).toString("utf8").slice(0, 4096),
|
|
1021
|
+
code: code ?? 1,
|
|
1022
|
+
});
|
|
1023
|
+
});
|
|
1024
|
+
proc.on("error", (err) => {
|
|
1025
|
+
resolve({ stdout: "", stderr: err.message, code: 1 });
|
|
1026
|
+
});
|
|
1027
|
+
});
|
|
1028
|
+
}
|
|
1029
|
+
async function executeQuery(body) {
|
|
1030
|
+
const rt = getAskDbRuntimeConfig();
|
|
1031
|
+
const { provider, databaseUrl, file } = rt.studio.execute;
|
|
1032
|
+
if (!isRecord(body) || typeof body.sql !== "string" || body.sql.trim() === "") {
|
|
1033
|
+
return { ok: false, error: "`sql` is required." };
|
|
1034
|
+
}
|
|
1035
|
+
const sql = body.sql;
|
|
1036
|
+
const params = Array.isArray(body.params) ? body.params : [];
|
|
1037
|
+
const def = EXECUTE_DRIVER_REGISTRY[provider];
|
|
1038
|
+
return def.execute({ connectionString: databaseUrl, file, sql, params });
|
|
1039
|
+
}
|
|
1040
|
+
function isLoopbackRequest(req) {
|
|
1041
|
+
const socket = req.socket;
|
|
1042
|
+
const remoteAddress = socket?.remoteAddress ?? "";
|
|
1043
|
+
return (remoteAddress === "127.0.0.1" ||
|
|
1044
|
+
remoteAddress === "::1" ||
|
|
1045
|
+
remoteAddress === "::ffff:127.0.0.1" ||
|
|
1046
|
+
remoteAddress === "localhost");
|
|
918
1047
|
}
|
|
919
1048
|
function readOptionalJson(path) {
|
|
920
1049
|
if (!existsSync(path))
|