@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.
@@ -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,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":"AAAA;;;;;;;;;;GAUG;AAyBH,8EAA8E;AAC9E,wBAAwB;AACxB,8EAA8E;AAE9E,MAAM,QAAQ,GAAG,GAAG,CAAC;AAErB,SAAS,eAAe,CACtB,OAAiB,EACjB,IAAiB,EACjB,QAAgB,EAChB,UAAkB;IAElB,MAAM,SAAS,GAAG,IAAI,CAAC,MAAM,GAAG,QAAQ,CAAC;IACzC,OAAO;QACL,EAAE,EAAE,IAAI;QACR,OAAO;QACP,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,QAAQ,CAAC;QAC7B,QAAQ;QACR,UAAU;QACV,SAAS;KACV,CAAC;AACJ,CAAC;AAkBD,KAAK,UAAU,eAAe,CAAC,KAAyB;IACtD,MAAM,EAAE,gBAAgB,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,KAAK,CAAC;IAChD,IAAI,CAAC,gBAAgB,EAAE,CAAC;QACtB,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,wIAAwI,EAAE,CAAC;IACxK,CAAC;IAED,IAAI,KAAY,CAAC;IACjB,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,CAAC;QAC/B,KAAK,GAAG,CAAE,GAAsC,CAAC,OAAO,IAAI,GAAG,CAAU,CAAC;IAC5E,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,mEAAmE,EAAE,CAAC;IACnG,CAAC;IAED,MAAM,MAAM,GAAG,IAAI,KAAK,CAAC,MAAM,CAAC,EAAE,gBAAgB,EAAE,CAAC,CAAC;IACtD,IAAI,CAAC;QACH,MAAM,MAAM,CAAC,OAAO,EAAE,CAAC;IACzB,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC;IAChF,CAAC;IACD,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IAC3B,IAAI,CAAC;QACH,MAAM,MAAM,CAAC,KAAK,CAAC,iBAAiB,CAAC,CAAC;QACtC,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,KAAK,CAAC,EAAE,IAAI,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;QACjE,MAAM,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;QAC7B,MAAM,UAAU,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,OAAO,CAAC;QACxC,MAAM,OAAO,GAAG,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;QACjD,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QAC9D,OAAO,eAAe,CAAC,OAAO,EAAE,IAAI,EAAE,MAAM,CAAC,QAAQ,IAAI,IAAI,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;IACpF,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAO,MAAM,CAAC,KAAK,CAAC,UAAU,CAAsB,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;QACrE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC;IAChF,CAAC;YAAS,CAAC;QACT,MAAM,MAAM,CAAC,GAAG,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;IACrC,CAAC;AACH,CAAC;AAeD,KAAK,UAAU,YAAY,CAAC,KAAyB;IACnD,MAAM,EAAE,gBAAgB,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,KAAK,CAAC;IAChD,IAAI,CAAC,gBAAgB,EAAE,CAAC;QACtB,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,kIAAkI,EAAE,CAAC;IAClK,CAAC;IAED,IAAI,SAAoB,CAAC;IACzB,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,gBAAgB,CAAC,CAAC;QAC3C,SAAS,GAAG,CAAE,GAA0C,CAAC,OAAO,IAAI,GAAG,CAAc,CAAC;IACxF,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,2EAA2E,EAAE,CAAC;IAC3G,CAAC;IAED,IAAI,IAAsB,CAAC;IAC3B,IAAI,CAAC;QACH,IAAI,GAAG,MAAM,SAAS,CAAC,gBAAgB,CAAC,EAAE,GAAG,EAAE,gBAAgB,EAAE,CAAC,CAAC;IACrE,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC;IAChF,CAAC;IACD,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IAC3B,IAAI,CAAC;QACH,MAAM,IAAI,CAAC,KAAK,CAAC,mCAAmC,CAAC,CAAC;QACtD,MAAM,IAAI,CAAC,KAAK,CAAC,mBAAmB,CAAC,CAAC;QACtC,MAAM,CAAC,OAAO,EAAE,MAAM,CAAC,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;QAC1D,MAAM,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;QAC7B,MAAM,UAAU,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,OAAO,CAAC;QACxC,MAAM,OAAO,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;QAC1C,MAAM,IAAI,GAAI,OAA0C,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QAC9F,OAAO,eAAe,CAAC,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;IACjE,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;QAC7C,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC;IAChF,CAAC;YAAS,CAAC;QACT,MAAM,IAAI,CAAC,GAAG,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;IACnC,CAAC;AACH,CAAC;AAkBD,KAAK,UAAU,aAAa,CAAC,KAAyB;IACpD,MAAM,EAAE,IAAI,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,KAAK,CAAC;IACpC,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,uHAAuH,EAAE,CAAC;IACvJ,CAAC;IAED,IAAI,OAAoC,CAAC;IACzC,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,gBAAgB,CAAC,CAAC;QAC3C,OAAO,GAAI,GAAmC,CAAC,OAAO,CAAC;IACzD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,2FAA2F,EAAE,CAAC;IAC3H,CAAC;IAED,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IAC3B,MAAM,EAAE,GAAG,IAAI,OAAO,CAAC,IAAI,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC;IACtE,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QAC7B,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;QAClD,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,MAAM,CAAmC,CAAC;QACtE,MAAM,UAAU,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,OAAO,CAAC;QACxC,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QAC1D,OAAO,eAAe,CAAC,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;IACjE,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC;IAChF,CAAC;YAAS,CAAC;QACT,EAAE,CAAC,KAAK,EAAE,CAAC;IACb,CAAC;AACH,CAAC;AAkBD;;;GAGG;AACH,SAAS,sBAAsB,CAAC,GAAW,EAAE,MAAiB;IAC5D,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAC;IACtD,MAAM,QAAQ,GAA4B,EAAE,CAAC;IAC7C,MAAM,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,GAAG,EAAE,EAAE;QAC5B,QAAQ,CAAC,IAAI,GAAG,EAAE,CAAC,GAAG,KAAK,CAAC;IAC9B,CAAC,CAAC,CAAC;IACH,uEAAuE;IACvE,IAAI,GAAG,GAAG,CAAC,CAAC;IACZ,MAAM,SAAS,GAAG,GAAG;SAClB,OAAO,CAAC,QAAQ,EAAE,GAAG,EAAE,CAAC,KAAK,GAAG,EAAE,EAAE,CAAC;SACrC,OAAO,CAAC,KAAK,EAAE,GAAG,EAAE,CAAC,KAAK,GAAG,EAAE,EAAE,CAAC,CAAC;IACtC,OAAO,EAAE,GAAG,EAAE,SAAS,EAAE,QAAQ,EAAE,CAAC;AACtC,CAAC;AAED,KAAK,UAAU,gBAAgB,CAAC,KAAyB;IACvD,MAAM,EAAE,gBAAgB,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,KAAK,CAAC;IAChD,IAAI,CAAC,gBAAgB,EAAE,CAAC;QACtB,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,2IAA2I,EAAE,CAAC;IAC3K,CAAC;IAED,IAAI,QAAkB,CAAC;IACvB,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,OAAO,CAAC,CAAC;QAClC,QAAQ,GAAG,CAAE,GAAyC,CAAC,OAAO,IAAI,GAAG,CAAa,CAAC;IACrF,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,yEAAyE,EAAE,CAAC;IACzG,CAAC;IAED,IAAI,IAAe,CAAC;IACpB,IAAI,CAAC;QACH,IAAI,GAAG,MAAM,IAAI,QAAQ,CAAC,cAAc,CAAC,gBAAgB,CAAC,CAAC,OAAO,EAAE,CAAC;IACvE,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC;IAChF,CAAC;IACD,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IAC3B,IAAI,CAAC;QACH,MAAM,EAAE,GAAG,EAAE,YAAY,EAAE,QAAQ,EAAE,GAAG,sBAAsB,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;QAC5E,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,EAAE,CAAC;QAC3B,KAAK,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAC;YACrD,GAAG,CAAC,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;QACzB,CAAC;QACD,MAAM,MAAM,GAAG,MAAM,GAAG,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC;QAC7C,MAAM,UAAU,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,OAAO,CAAC;QACxC,MAAM,OAAO,GAAG,MAAM,CAAC,SAAS,IAAI,EAAE,CAAC;QACvC,MAAM,OAAO,GAAG,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;QAClE,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QAC1D,OAAO,eAAe,CAAC,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;IACjE,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC;IAChF,CAAC;YAAS,CAAC;QACT,MAAM,IAAI,CAAC,KAAK,EAAE,CAAC;IACrB,CAAC;AACH,CAAC;AAED,8EAA8E;AAC9E,WAAW;AACX,8EAA8E;AAE9E,MAAM,CAAC,MAAM,uBAAuB,GAA0D;IAC5F,QAAQ,EAAE;QACR,QAAQ,EAAE,UAAU;QACpB,KAAK,EAAE,UAAU;QACjB,WAAW,EAAE,IAAI;QACjB,cAAc,EAAE,aAAa;QAC7B,OAAO,EAAE,eAAe;KACzB;IACD,KAAK,EAAE;QACL,QAAQ,EAAE,OAAO;QACjB,KAAK,EAAE,OAAO;QACd,WAAW,EAAE,QAAQ;QACrB,cAAc,EAAE,iBAAiB;QACjC,OAAO,EAAE,YAAY;KACtB;IACD,MAAM,EAAE;QACN,QAAQ,EAAE,QAAQ;QAClB,KAAK,EAAE,QAAQ;QACf,WAAW,EAAE,gBAAgB;QAC7B,cAAc,EAAE,yBAAyB;QACzC,OAAO,EAAE,aAAa;KACvB;IACD,SAAS,EAAE;QACT,QAAQ,EAAE,WAAW;QACrB,KAAK,EAAE,YAAY;QACnB,WAAW,EAAE,OAAO;QACpB,cAAc,EAAE,gBAAgB;QAChC,OAAO,EAAE,gBAAgB;KAC1B;CACF,CAAC;AAEF;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CAAC,WAAmB;IACzD,IAAI,CAAC;QACH,MAAM,MAAM,CAAC,WAAW,CAAC,CAAC;QAC1B,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC"}
@@ -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,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 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() {
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
- if (!isRecord(body) || typeof body.sql !== "string" || body.sql.trim() === "") {
876
- return { ok: false, error: "`sql` is required." };
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
- 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);
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
- catch {
886
- return { ok: false, error: "The `pg` package is required for query execution. Install it with `pnpm add pg`." };
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
- 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;
967
+ if (existsSync(join(projectRoot, "package-lock.json"))) {
899
968
  return {
900
- ok: true,
901
- columns,
902
- rows: rows.slice(0, 500),
903
- rowCount: result.rowCount ?? rows.length,
904
- durationMs,
905
- truncated,
969
+ packageManager: "npm",
970
+ command: "npm",
971
+ args: ["install", packageName],
972
+ manualReason: null,
906
973
  };
907
974
  }
908
- catch (err) {
909
- await client.query("ROLLBACK").catch(() => { });
975
+ if (existsSync(join(projectRoot, "yarn.lock"))) {
910
976
  return {
911
- ok: false,
912
- error: err instanceof Error ? err.message : String(err),
977
+ packageManager: "yarn",
978
+ command: "yarn",
979
+ args: ["add", packageName],
980
+ manualReason: null,
913
981
  };
914
982
  }
915
- finally {
916
- await client.end();
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))