@askdb/studio 0.2.0-beta.24 → 0.2.0-beta.26

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.
@@ -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-_lnkmWaK.js"></script>
10
- <link rel="stylesheet" crossorigin href="/assets/index-DY6BxO-7.css">
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,38 @@
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
+ /** Absolute path to the user's project root, used for driver resolution. */
20
+ projectRoot: string;
21
+ };
22
+ export type StudioDriverDefinition = {
23
+ provider: StudioExecuteProvider;
24
+ label: string;
25
+ packageName: "pg" | "mysql2" | "better-sqlite3" | "mssql";
26
+ installCommand: string;
27
+ execute(input: StudioExecuteInput): Promise<ExecuteResponse>;
28
+ };
29
+ export declare const EXECUTE_DRIVER_REGISTRY: Record<StudioExecuteProvider, StudioDriverDefinition>;
30
+ /**
31
+ * Check whether a driver package is resolvable from the user's project root.
32
+ *
33
+ * Uses `createRequire` so that packages installed in the project's own
34
+ * node_modules are found even when Studio runs from the global npx cache,
35
+ * where a bare `import(packageName)` would not see them.
36
+ */
37
+ export declare function isDriverInstalled(packageName: string, projectRoot: string): boolean;
38
+ //# 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;AAKH,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;IAClB,4EAA4E;IAC5E,WAAW,EAAE,MAAM,CAAC;CACrB,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;AAqSF,eAAO,MAAM,uBAAuB,EAAE,MAAM,CAAC,qBAAqB,EAAE,sBAAsB,CA6BzF,CAAC;AAEF;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,WAAW,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,OAAO,CAEnF"}
@@ -0,0 +1,277 @@
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 { createRequire } from "node:module";
13
+ import { join } from "node:path";
14
+ import { pathToFileURL } from "node:url";
15
+ // ---------------------------------------------------------------------------
16
+ // Driver resolution helpers
17
+ // ---------------------------------------------------------------------------
18
+ /**
19
+ * Resolve the absolute entry-point path of a driver package from the user's
20
+ * project root. Uses `createRequire` so that packages installed in the
21
+ * user's own node_modules are found even when Studio is running from the
22
+ * global npx cache (where bare `import(packageName)` cannot see them).
23
+ *
24
+ * The stub filename passed to `createRequire` never needs to exist on disk —
25
+ * Node only uses its directory as the starting point for resolution.
26
+ */
27
+ function resolveDriverPath(packageName, projectRoot) {
28
+ try {
29
+ const req = createRequire(join(projectRoot, "__stub__.js"));
30
+ return req.resolve(packageName);
31
+ }
32
+ catch {
33
+ return null;
34
+ }
35
+ }
36
+ /**
37
+ * Import a driver package resolved from the user's project root.
38
+ * Falls back to a bare import (works when Studio itself has the package)
39
+ * but the project-root resolution path is tried first.
40
+ */
41
+ async function importDriver(packageName, projectRoot) {
42
+ const resolved = resolveDriverPath(packageName, projectRoot);
43
+ if (resolved) {
44
+ // Convert the absolute path to a file URL so dynamic import accepts it
45
+ // on all platforms, including Windows.
46
+ return import(pathToFileURL(resolved).href);
47
+ }
48
+ // Last resort: bare specifier (will work if Studio ships the package itself)
49
+ return import(packageName);
50
+ }
51
+ // ---------------------------------------------------------------------------
52
+ // Normalization helpers
53
+ // ---------------------------------------------------------------------------
54
+ const MAX_ROWS = 500;
55
+ function buildOkResponse(columns, rows, rowCount, durationMs) {
56
+ const truncated = rows.length > MAX_ROWS;
57
+ return {
58
+ ok: true,
59
+ columns,
60
+ rows: rows.slice(0, MAX_ROWS),
61
+ rowCount,
62
+ durationMs,
63
+ truncated,
64
+ };
65
+ }
66
+ async function executePostgres(input) {
67
+ const { connectionString, sql, params, projectRoot } = input;
68
+ if (!connectionString) {
69
+ return { ok: false, error: "No connection URL configured for Postgres execute. Set ASKDB_STUDIO_DATABASE_URL or introspection.providerConfig.postgres.databaseUrl." };
70
+ }
71
+ let pgMod;
72
+ try {
73
+ const mod = await importDriver("pg", projectRoot);
74
+ pgMod = (mod.default ?? mod);
75
+ }
76
+ catch {
77
+ return { ok: false, error: "The `pg` package is not installed. Install it with `pnpm add pg`." };
78
+ }
79
+ const client = new pgMod.Client({ connectionString });
80
+ try {
81
+ await client.connect();
82
+ }
83
+ catch (err) {
84
+ return { ok: false, error: err instanceof Error ? err.message : String(err) };
85
+ }
86
+ const startMs = Date.now();
87
+ try {
88
+ await client.query("BEGIN READ ONLY");
89
+ const result = await client.query({ text: sql, values: params });
90
+ await client.query("COMMIT");
91
+ const durationMs = Date.now() - startMs;
92
+ const columns = result.fields.map((f) => f.name);
93
+ const rows = result.rows.map((r) => columns.map((c) => r[c]));
94
+ return buildOkResponse(columns, rows, result.rowCount ?? rows.length, durationMs);
95
+ }
96
+ catch (err) {
97
+ await client.query("ROLLBACK").catch(() => { });
98
+ return { ok: false, error: err instanceof Error ? err.message : String(err) };
99
+ }
100
+ finally {
101
+ await client.end().catch(() => { });
102
+ }
103
+ }
104
+ async function executeMySQL(input) {
105
+ const { connectionString, sql, params, projectRoot } = input;
106
+ if (!connectionString) {
107
+ return { ok: false, error: "No connection URL configured for MySQL execute. Set ASKDB_STUDIO_DATABASE_URL or introspection.providerConfig.mysql.databaseUrl." };
108
+ }
109
+ let mysql2Mod;
110
+ try {
111
+ // mysql2/promise resolves through mysql2's exports map; resolve mysql2 first
112
+ // then let Node follow the exports map from the package root.
113
+ const mod = await importDriver("mysql2/promise", projectRoot);
114
+ mysql2Mod = (mod.default ?? mod);
115
+ }
116
+ catch {
117
+ return { ok: false, error: "The `mysql2` package is not installed. Install it with `pnpm add mysql2`." };
118
+ }
119
+ let conn;
120
+ try {
121
+ conn = await mysql2Mod.createConnection({ uri: connectionString });
122
+ }
123
+ catch (err) {
124
+ return { ok: false, error: err instanceof Error ? err.message : String(err) };
125
+ }
126
+ const startMs = Date.now();
127
+ try {
128
+ await conn.query("SET SESSION TRANSACTION READ ONLY");
129
+ await conn.query("START TRANSACTION");
130
+ const [rowsRaw, fields] = await conn.execute(sql, params);
131
+ await conn.query("ROLLBACK");
132
+ const durationMs = Date.now() - startMs;
133
+ const columns = fields.map((f) => f.name);
134
+ const rows = rowsRaw.map((r) => columns.map((c) => r[c]));
135
+ return buildOkResponse(columns, rows, rows.length, durationMs);
136
+ }
137
+ catch (err) {
138
+ await conn.query("ROLLBACK").catch(() => { });
139
+ return { ok: false, error: err instanceof Error ? err.message : String(err) };
140
+ }
141
+ finally {
142
+ await conn.end().catch(() => { });
143
+ }
144
+ }
145
+ async function executeSQLite(input) {
146
+ const { file, sql, params, projectRoot } = input;
147
+ if (!file) {
148
+ return { ok: false, error: "No SQLite file path configured for execute. Set ASKDB_STUDIO_SQLITE_FILE or introspection.providerConfig.sqlite.file." };
149
+ }
150
+ let Sqlite3;
151
+ try {
152
+ const mod = await importDriver("better-sqlite3", projectRoot);
153
+ Sqlite3 = mod.default;
154
+ }
155
+ catch {
156
+ return { ok: false, error: "The `better-sqlite3` package is not installed. Install it with `pnpm add better-sqlite3`." };
157
+ }
158
+ const startMs = Date.now();
159
+ const db = new Sqlite3(file, { readonly: true, fileMustExist: true });
160
+ try {
161
+ const stmt = db.prepare(sql);
162
+ const columns = stmt.columns().map((c) => c.name);
163
+ const rowsRaw = stmt.all(...params);
164
+ const durationMs = Date.now() - startMs;
165
+ const rows = rowsRaw.map((r) => columns.map((c) => r[c]));
166
+ return buildOkResponse(columns, rows, rows.length, durationMs);
167
+ }
168
+ catch (err) {
169
+ return { ok: false, error: err instanceof Error ? err.message : String(err) };
170
+ }
171
+ finally {
172
+ db.close();
173
+ }
174
+ }
175
+ /**
176
+ * Rewrite `$1`, `$2`, ... or bare `?` positional params to MSSQL `@p0`, `@p1`, ...
177
+ * and return an object of named bindings.
178
+ */
179
+ function rewriteSqlServerParams(sql, params) {
180
+ if (params.length === 0)
181
+ return { sql, bindings: {} };
182
+ const bindings = {};
183
+ params.forEach((value, idx) => {
184
+ bindings[`p${idx}`] = value;
185
+ });
186
+ // Replace $N placeholders (1-indexed) first, then bare ? placeholders.
187
+ let idx = 0;
188
+ const rewritten = sql
189
+ .replace(/\$\d+/g, () => `@p${idx++}`)
190
+ .replace(/\?/g, () => `@p${idx++}`);
191
+ return { sql: rewritten, bindings };
192
+ }
193
+ async function executeSQLServer(input) {
194
+ const { connectionString, sql, params, projectRoot } = input;
195
+ if (!connectionString) {
196
+ return { ok: false, error: "No connection URL configured for SQL Server execute. Set ASKDB_STUDIO_DATABASE_URL or introspection.providerConfig.sqlserver.databaseUrl." };
197
+ }
198
+ let mssqlMod;
199
+ try {
200
+ const mod = await importDriver("mssql", projectRoot);
201
+ mssqlMod = (mod.default ?? mod);
202
+ }
203
+ catch {
204
+ return { ok: false, error: "The `mssql` package is not installed. Install it with `pnpm add mssql`." };
205
+ }
206
+ let pool;
207
+ try {
208
+ pool = await new mssqlMod.ConnectionPool(connectionString).connect();
209
+ }
210
+ catch (err) {
211
+ return { ok: false, error: err instanceof Error ? err.message : String(err) };
212
+ }
213
+ const startMs = Date.now();
214
+ try {
215
+ const { sql: rewrittenSql, bindings } = rewriteSqlServerParams(sql, params);
216
+ const req = pool.request();
217
+ for (const [name, value] of Object.entries(bindings)) {
218
+ req.input(name, value);
219
+ }
220
+ const result = await req.query(rewrittenSql);
221
+ const durationMs = Date.now() - startMs;
222
+ const rowsRaw = result.recordset ?? [];
223
+ const columns = rowsRaw.length > 0 ? Object.keys(rowsRaw[0]) : [];
224
+ const rows = rowsRaw.map((r) => columns.map((c) => r[c]));
225
+ return buildOkResponse(columns, rows, rows.length, durationMs);
226
+ }
227
+ catch (err) {
228
+ return { ok: false, error: err instanceof Error ? err.message : String(err) };
229
+ }
230
+ finally {
231
+ await pool.close();
232
+ }
233
+ }
234
+ // ---------------------------------------------------------------------------
235
+ // Registry
236
+ // ---------------------------------------------------------------------------
237
+ export const EXECUTE_DRIVER_REGISTRY = {
238
+ postgres: {
239
+ provider: "postgres",
240
+ label: "Postgres",
241
+ packageName: "pg",
242
+ installCommand: "pnpm add pg",
243
+ execute: executePostgres,
244
+ },
245
+ mysql: {
246
+ provider: "mysql",
247
+ label: "MySQL",
248
+ packageName: "mysql2",
249
+ installCommand: "pnpm add mysql2",
250
+ execute: executeMySQL,
251
+ },
252
+ sqlite: {
253
+ provider: "sqlite",
254
+ label: "SQLite",
255
+ packageName: "better-sqlite3",
256
+ installCommand: "pnpm add better-sqlite3",
257
+ execute: executeSQLite,
258
+ },
259
+ sqlserver: {
260
+ provider: "sqlserver",
261
+ label: "SQL Server",
262
+ packageName: "mssql",
263
+ installCommand: "pnpm add mssql",
264
+ execute: executeSQLServer,
265
+ },
266
+ };
267
+ /**
268
+ * Check whether a driver package is resolvable from the user's project root.
269
+ *
270
+ * Uses `createRequire` so that packages installed in the project's own
271
+ * node_modules are found even when Studio runs from the global npx cache,
272
+ * where a bare `import(packageName)` would not see them.
273
+ */
274
+ export function isDriverInstalled(packageName, projectRoot) {
275
+ return resolveDriverPath(packageName, projectRoot) !== null;
276
+ }
277
+ //# 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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"}
@@ -1 +1 @@
1
- {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,YAAY,EAA6C,MAAM,WAAW,CAAC;AA8BpF,OAAO,EAOL,mBAAmB,EAOpB,MAAM,YAAY,CAAC;AACpB,OAAO,EAaL,KAAK,SAAS,EACf,MAAM,eAAe,CAAC;AACvB,OAAO,KAAK,EASV,kBAAkB,EAGnB,MAAM,iBAAiB,CAAC;AAMzB,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,CAqGvE;AAED,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,SAAS,GAAG,kBAAkB,CAyC3E"}
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,9 +107,21 @@ 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(state.schemaDir);
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
- const result = await executeQuery(body);
124
+ const result = await executeQuery(body, state.schemaDir);
111
125
  return writeJson(res, 200, result);
112
126
  }
113
127
  if (req.method === "GET" && !url.pathname.startsWith("/api/")) {
@@ -865,56 +879,174 @@ function parsePlaygroundHistoryEntry(body) {
865
879
  return body;
866
880
  }
867
881
  // ---------------------------------------------------------------------------
868
- // Execute endpoint helper
882
+ // Execute endpoint helpers
869
883
  // ---------------------------------------------------------------------------
870
- async function executeQuery(body) {
871
- const databaseUrl = getAskDbRuntimeConfig().studio.execute.databaseUrl;
872
- if (!databaseUrl) {
873
- return { ok: false, error: "ASKDB_STUDIO_DATABASE_URL is not configured" };
884
+ async function getExecuteStatus(schemaDir) {
885
+ const rt = getAskDbRuntimeConfig();
886
+ const { provider, databaseUrl, file } = rt.studio.execute;
887
+ const def = EXECUTE_DRIVER_REGISTRY[provider];
888
+ const projectRoot = findProjectRoot(schemaDir) ?? schemaDir;
889
+ const installed = isDriverInstalled(def.packageName, projectRoot);
890
+ const connectionKind = provider === "sqlite" ? "file" : "url";
891
+ const configured = connectionKind === "file" ? Boolean(file) : Boolean(databaseUrl);
892
+ return {
893
+ provider,
894
+ label: def.label,
895
+ configured,
896
+ connectionKind,
897
+ packageName: def.packageName,
898
+ installed,
899
+ installCommand: def.installCommand,
900
+ canInstallFromStudio: true,
901
+ manualInstallReason: null,
902
+ };
903
+ }
904
+ async function installExecuteDriver(body, schemaDir) {
905
+ const rt = getAskDbRuntimeConfig();
906
+ const configuredProvider = rt.studio.execute.provider;
907
+ // Accept the configured provider or an explicit allowlisted provider.
908
+ let provider = configuredProvider;
909
+ if (isRecord(body) && typeof body.provider === "string") {
910
+ const requested = body.provider;
911
+ if (!(requested in EXECUTE_DRIVER_REGISTRY)) {
912
+ return {
913
+ ok: false,
914
+ provider: configuredProvider,
915
+ packageName: EXECUTE_DRIVER_REGISTRY[configuredProvider].packageName,
916
+ command: [],
917
+ error: `Unknown provider: ${body.provider}`,
918
+ installed: false,
919
+ };
920
+ }
921
+ provider = requested;
874
922
  }
875
- if (!isRecord(body) || typeof body.sql !== "string" || body.sql.trim() === "") {
876
- return { ok: false, error: "`sql` is required." };
923
+ const def = EXECUTE_DRIVER_REGISTRY[provider];
924
+ const { packageManager, command, args, manualReason } = detectPackageManager(schemaDir, def.packageName);
925
+ if (manualReason) {
926
+ return {
927
+ ok: false,
928
+ provider,
929
+ packageName: def.packageName,
930
+ command: [],
931
+ error: manualReason,
932
+ installed: false,
933
+ };
877
934
  }
878
- const sql = body.sql;
879
- const params = Array.isArray(body.params) ? body.params : [];
880
- let pgMod;
881
- try {
882
- const mod = await import("pg");
883
- pgMod = (mod.default ?? mod);
935
+ // Run the install.
936
+ const { stdout, stderr, code } = await spawnCommand(command, args, schemaDir);
937
+ const projectRoot = findProjectRoot(schemaDir) ?? schemaDir;
938
+ const installed = isDriverInstalled(def.packageName, projectRoot);
939
+ return {
940
+ ok: code === 0,
941
+ provider,
942
+ packageName: def.packageName,
943
+ command: [command, ...args],
944
+ stdout,
945
+ stderr,
946
+ error: code !== 0 ? `${packageManager} exited with code ${code}` : undefined,
947
+ installed,
948
+ };
949
+ }
950
+ function detectPackageManager(cwd, packageName) {
951
+ // Walk up from schemaDir to find the project root's lockfile.
952
+ const projectRoot = findProjectRoot(cwd);
953
+ if (!projectRoot) {
954
+ return {
955
+ packageManager: "",
956
+ command: "",
957
+ args: [],
958
+ manualReason: `Could not locate a package.json above ${cwd}. Run the install manually.`,
959
+ };
884
960
  }
885
- catch {
886
- return { ok: false, error: "The `pg` package is required for query execution. Install it with `pnpm add pg`." };
961
+ if (existsSync(join(projectRoot, "pnpm-lock.yaml"))) {
962
+ return {
963
+ packageManager: "pnpm",
964
+ command: "pnpm",
965
+ args: ["add", packageName],
966
+ manualReason: null,
967
+ };
887
968
  }
888
- const client = new pgMod.Client({ connectionString: databaseUrl });
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;
969
+ if (existsSync(join(projectRoot, "package-lock.json"))) {
899
970
  return {
900
- ok: true,
901
- columns,
902
- rows: rows.slice(0, 500),
903
- rowCount: result.rowCount ?? rows.length,
904
- durationMs,
905
- truncated,
971
+ packageManager: "npm",
972
+ command: "npm",
973
+ args: ["install", packageName],
974
+ manualReason: null,
906
975
  };
907
976
  }
908
- catch (err) {
909
- await client.query("ROLLBACK").catch(() => { });
977
+ if (existsSync(join(projectRoot, "yarn.lock"))) {
910
978
  return {
911
- ok: false,
912
- error: err instanceof Error ? err.message : String(err),
979
+ packageManager: "yarn",
980
+ command: "yarn",
981
+ args: ["add", packageName],
982
+ manualReason: null,
913
983
  };
914
984
  }
915
- finally {
916
- await client.end();
985
+ if (existsSync(join(projectRoot, "bun.lockb")) || existsSync(join(projectRoot, "bun.lock"))) {
986
+ return {
987
+ packageManager: "bun",
988
+ command: "bun",
989
+ args: ["add", packageName],
990
+ manualReason: null,
991
+ };
992
+ }
993
+ return {
994
+ packageManager: "",
995
+ command: "",
996
+ args: [],
997
+ manualReason: `No recognized lockfile found in ${projectRoot}. Run: pnpm add ${packageName}`,
998
+ };
999
+ }
1000
+ function findProjectRoot(startDir) {
1001
+ let current = resolve(startDir);
1002
+ for (let i = 0; i < 20; i++) {
1003
+ if (existsSync(join(current, "package.json")))
1004
+ return current;
1005
+ const parent = dirname(current);
1006
+ if (parent === current)
1007
+ return null;
1008
+ current = parent;
1009
+ }
1010
+ return null;
1011
+ }
1012
+ function spawnCommand(command, args, cwd) {
1013
+ return new Promise((resolve) => {
1014
+ const proc = spawn(command, args, { cwd, shell: false, stdio: ["ignore", "pipe", "pipe"] });
1015
+ const stdoutChunks = [];
1016
+ const stderrChunks = [];
1017
+ proc.stdout.on("data", (chunk) => stdoutChunks.push(chunk));
1018
+ proc.stderr.on("data", (chunk) => stderrChunks.push(chunk));
1019
+ proc.on("close", (code) => {
1020
+ resolve({
1021
+ stdout: Buffer.concat(stdoutChunks).toString("utf8").slice(0, 4096),
1022
+ stderr: Buffer.concat(stderrChunks).toString("utf8").slice(0, 4096),
1023
+ code: code ?? 1,
1024
+ });
1025
+ });
1026
+ proc.on("error", (err) => {
1027
+ resolve({ stdout: "", stderr: err.message, code: 1 });
1028
+ });
1029
+ });
1030
+ }
1031
+ async function executeQuery(body, schemaDir) {
1032
+ const rt = getAskDbRuntimeConfig();
1033
+ const { provider, databaseUrl, file } = rt.studio.execute;
1034
+ if (!isRecord(body) || typeof body.sql !== "string" || body.sql.trim() === "") {
1035
+ return { ok: false, error: "`sql` is required." };
917
1036
  }
1037
+ const sql = body.sql;
1038
+ const params = Array.isArray(body.params) ? body.params : [];
1039
+ const projectRoot = findProjectRoot(schemaDir) ?? schemaDir;
1040
+ const def = EXECUTE_DRIVER_REGISTRY[provider];
1041
+ return def.execute({ connectionString: databaseUrl, file, sql, params, projectRoot });
1042
+ }
1043
+ function isLoopbackRequest(req) {
1044
+ const socket = req.socket;
1045
+ const remoteAddress = socket?.remoteAddress ?? "";
1046
+ return (remoteAddress === "127.0.0.1" ||
1047
+ remoteAddress === "::1" ||
1048
+ remoteAddress === "::ffff:127.0.0.1" ||
1049
+ remoteAddress === "localhost");
918
1050
  }
919
1051
  function readOptionalJson(path) {
920
1052
  if (!existsSync(path))