@squadbase/vite-server 0.0.1-build-7 → 0.0.1-build-9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli/index.js CHANGED
@@ -82,10 +82,11 @@ var init_interactive = __esm({
82
82
  // src/cli/index.ts
83
83
  import { parseArgs } from "util";
84
84
  import path4 from "path";
85
- import { readFile as readFile3 } from "fs/promises";
85
+ import { readFile as readFile4 } from "fs/promises";
86
86
 
87
87
  // src/connector-client/registry.ts
88
88
  import { readFileSync, watch as fsWatch } from "fs";
89
+ import { readFile } from "fs/promises";
89
90
  import path from "path";
90
91
 
91
92
  // src/connector-client/postgresql.ts
@@ -113,6 +114,11 @@ function resolveEnvVar(entry, key, connectionId) {
113
114
  }
114
115
  return value;
115
116
  }
117
+ function resolveEnvVarOptional(entry, key) {
118
+ const envVarName = entry.envVars[key];
119
+ if (!envVarName) return void 0;
120
+ return process.env[envVarName] || void 0;
121
+ }
116
122
 
117
123
  // src/connector-client/bigquery.ts
118
124
  function createBigQueryClient(entry, connectionId) {
@@ -124,7 +130,7 @@ function createBigQueryClient(entry, connectionId) {
124
130
  gcpCredentials = JSON.parse(serviceAccountJson);
125
131
  } catch {
126
132
  throw new Error(
127
- `BigQuery service account JSON (decoded from base64) is not valid JSON for connection "${connectionId}"`
133
+ `BigQuery service account JSON (decoded from base64) is not valid JSON for connectionId "${connectionId}"`
128
134
  );
129
135
  }
130
136
  return {
@@ -181,26 +187,193 @@ function createSnowflakeClient(entry, connectionId) {
181
187
  };
182
188
  }
183
189
 
190
+ // src/connector-client/mysql.ts
191
+ function createMySQLClient(entry, connectionId) {
192
+ const connectionUrl = resolveEnvVar(entry, "connection-url", connectionId);
193
+ let poolPromise = null;
194
+ function getPool() {
195
+ if (!poolPromise) {
196
+ poolPromise = import("mysql2/promise").then(
197
+ (mysql) => mysql.default.createPool(connectionUrl)
198
+ );
199
+ }
200
+ return poolPromise;
201
+ }
202
+ return {
203
+ async query(sql, params) {
204
+ const pool = await getPool();
205
+ const [rows] = await pool.execute(sql, params);
206
+ return { rows };
207
+ }
208
+ };
209
+ }
210
+
211
+ // src/connector-client/aws-athena.ts
212
+ function createAthenaClient(entry, connectionId) {
213
+ const region = resolveEnvVar(entry, "aws-region", connectionId);
214
+ const accessKeyId = resolveEnvVar(entry, "aws-access-key-id", connectionId);
215
+ const secretAccessKey = resolveEnvVar(entry, "aws-secret-access-key", connectionId);
216
+ const workgroup = resolveEnvVarOptional(entry, "workgroup") ?? "primary";
217
+ const outputLocation = resolveEnvVarOptional(entry, "output-location");
218
+ return {
219
+ async query(sql) {
220
+ const {
221
+ AthenaClient,
222
+ StartQueryExecutionCommand,
223
+ GetQueryExecutionCommand,
224
+ GetQueryResultsCommand
225
+ } = await import("@aws-sdk/client-athena");
226
+ const client = new AthenaClient({
227
+ region,
228
+ credentials: { accessKeyId, secretAccessKey }
229
+ });
230
+ const startParams = {
231
+ QueryString: sql,
232
+ WorkGroup: workgroup
233
+ };
234
+ if (outputLocation) {
235
+ startParams.ResultConfiguration = { OutputLocation: outputLocation };
236
+ }
237
+ const { QueryExecutionId } = await client.send(
238
+ new StartQueryExecutionCommand(startParams)
239
+ );
240
+ if (!QueryExecutionId) throw new Error("Athena: failed to start query execution");
241
+ while (true) {
242
+ const { QueryExecution } = await client.send(
243
+ new GetQueryExecutionCommand({ QueryExecutionId })
244
+ );
245
+ const state = QueryExecution?.Status?.State;
246
+ if (state === "SUCCEEDED") break;
247
+ if (state === "FAILED") {
248
+ throw new Error(
249
+ `Athena query failed: ${QueryExecution?.Status?.StateChangeReason ?? "unknown"}`
250
+ );
251
+ }
252
+ if (state === "CANCELLED") throw new Error("Athena query was cancelled");
253
+ await new Promise((r) => setTimeout(r, 500));
254
+ }
255
+ const { ResultSet } = await client.send(
256
+ new GetQueryResultsCommand({ QueryExecutionId })
257
+ );
258
+ const resultRows = ResultSet?.Rows ?? [];
259
+ if (resultRows.length === 0) return { rows: [] };
260
+ const headers = resultRows[0].Data?.map((d) => d.VarCharValue ?? "") ?? [];
261
+ const rows = resultRows.slice(1).map((row) => {
262
+ const obj = {};
263
+ row.Data?.forEach((d, i) => {
264
+ obj[headers[i]] = d.VarCharValue ?? null;
265
+ });
266
+ return obj;
267
+ });
268
+ return { rows };
269
+ }
270
+ };
271
+ }
272
+
273
+ // src/connector-client/redshift.ts
274
+ function createRedshiftClient(entry, connectionId) {
275
+ const region = resolveEnvVar(entry, "aws-region", connectionId);
276
+ const accessKeyId = resolveEnvVar(entry, "aws-access-key-id", connectionId);
277
+ const secretAccessKey = resolveEnvVar(entry, "aws-secret-access-key", connectionId);
278
+ const database = resolveEnvVar(entry, "database", connectionId);
279
+ const clusterIdentifier = resolveEnvVarOptional(entry, "cluster-identifier");
280
+ const workgroupName = resolveEnvVarOptional(entry, "workgroup-name");
281
+ const secretArn = resolveEnvVarOptional(entry, "secret-arn");
282
+ const dbUser = resolveEnvVarOptional(entry, "db-user");
283
+ return {
284
+ async query(sql) {
285
+ const {
286
+ RedshiftDataClient,
287
+ ExecuteStatementCommand,
288
+ DescribeStatementCommand,
289
+ GetStatementResultCommand
290
+ } = await import("@aws-sdk/client-redshift-data");
291
+ const client = new RedshiftDataClient({
292
+ region,
293
+ credentials: { accessKeyId, secretAccessKey }
294
+ });
295
+ const executeParams = {
296
+ Sql: sql,
297
+ Database: database
298
+ };
299
+ if (clusterIdentifier) executeParams.ClusterIdentifier = clusterIdentifier;
300
+ if (workgroupName) executeParams.WorkgroupName = workgroupName;
301
+ if (secretArn) executeParams.SecretArn = secretArn;
302
+ if (dbUser) executeParams.DbUser = dbUser;
303
+ const { Id } = await client.send(
304
+ new ExecuteStatementCommand(executeParams)
305
+ );
306
+ if (!Id) throw new Error("Redshift: failed to start statement execution");
307
+ while (true) {
308
+ const desc = await client.send(new DescribeStatementCommand({ Id }));
309
+ const status = desc.Status;
310
+ if (status === "FINISHED") break;
311
+ if (status === "FAILED") {
312
+ throw new Error(`Redshift query failed: ${desc.Error ?? "unknown"}`);
313
+ }
314
+ if (status === "ABORTED") throw new Error("Redshift query was aborted");
315
+ await new Promise((r) => setTimeout(r, 500));
316
+ }
317
+ const result = await client.send(new GetStatementResultCommand({ Id }));
318
+ const columns = result.ColumnMetadata?.map((c) => c.name ?? "") ?? [];
319
+ const rows = (result.Records ?? []).map((record) => {
320
+ const obj = {};
321
+ record.forEach((field, i) => {
322
+ const col = columns[i];
323
+ const value = field.stringValue ?? field.longValue ?? field.doubleValue ?? field.booleanValue ?? (field.isNull ? null : field.blobValue ?? null);
324
+ obj[col] = value;
325
+ });
326
+ return obj;
327
+ });
328
+ return { rows };
329
+ }
330
+ };
331
+ }
332
+
333
+ // src/connector-client/databricks.ts
334
+ function createDatabricksClient(entry, connectionId) {
335
+ const host = resolveEnvVar(entry, "host", connectionId);
336
+ const httpPath = resolveEnvVar(entry, "http-path", connectionId);
337
+ const token = resolveEnvVar(entry, "token", connectionId);
338
+ return {
339
+ async query(sql) {
340
+ const { DBSQLClient } = await import("@databricks/sql");
341
+ const client = new DBSQLClient();
342
+ await client.connect({ host, path: httpPath, token });
343
+ try {
344
+ const session = await client.openSession();
345
+ try {
346
+ const operation = await session.executeStatement(sql);
347
+ const result = await operation.fetchAll();
348
+ await operation.close();
349
+ return { rows: result };
350
+ } finally {
351
+ await session.close();
352
+ }
353
+ } finally {
354
+ await client.close();
355
+ }
356
+ }
357
+ };
358
+ }
359
+
184
360
  // src/connector-client/registry.ts
185
361
  function createConnectorRegistry() {
186
- let connectionsCache = null;
187
362
  const clientCache = /* @__PURE__ */ new Map();
188
363
  function getConnectionsFilePath() {
189
364
  return process.env.CONNECTIONS_PATH ?? path.join(process.cwd(), ".squadbase/connections.json");
190
365
  }
191
- function loadConnections() {
192
- if (connectionsCache !== null) return connectionsCache;
366
+ async function loadConnections2() {
193
367
  const filePath = getConnectionsFilePath();
194
368
  try {
195
- const raw = readFileSync(filePath, "utf-8");
196
- connectionsCache = JSON.parse(raw);
369
+ const raw = await readFile(filePath, "utf-8");
370
+ return JSON.parse(raw);
197
371
  } catch {
198
- connectionsCache = {};
372
+ return {};
199
373
  }
200
- return connectionsCache;
201
374
  }
202
375
  async function getClient2(connectionId) {
203
- const connections = loadConnections();
376
+ const connections = await loadConnections2();
204
377
  const entry = connections[connectionId];
205
378
  if (!entry) {
206
379
  throw new Error(`connection '${connectionId}' not found in .squadbase/connections.json`);
@@ -214,6 +387,20 @@ function createConnectorRegistry() {
214
387
  if (connectorSlug === "bigquery") {
215
388
  return { client: createBigQueryClient(entry, connectionId), connectorSlug };
216
389
  }
390
+ if (connectorSlug === "athena") {
391
+ return { client: createAthenaClient(entry, connectionId), connectorSlug };
392
+ }
393
+ if (connectorSlug === "redshift") {
394
+ return { client: createRedshiftClient(entry, connectionId), connectorSlug };
395
+ }
396
+ if (connectorSlug === "databricks") {
397
+ return { client: createDatabricksClient(entry, connectionId), connectorSlug };
398
+ }
399
+ if (connectorSlug === "mysql") {
400
+ const client = createMySQLClient(entry, connectionId);
401
+ clientCache.set(connectionId, client);
402
+ return { client, connectorSlug };
403
+ }
217
404
  if (connectorSlug === "postgresql" || connectorSlug === "squadbase-db") {
218
405
  const urlEnvName = entry.envVars["connection-url"];
219
406
  if (!urlEnvName) {
@@ -230,7 +417,7 @@ function createConnectorRegistry() {
230
417
  return { client, connectorSlug };
231
418
  }
232
419
  throw new Error(
233
- `connector type '${connectorSlug}' is not supported. Supported: "snowflake", "bigquery", "postgresql", "squadbase-db"`
420
+ `connector type '${connectorSlug}' is not supported as a SQL connector. Supported SQL types: "postgresql", "squadbase-db", "mysql", "snowflake", "bigquery", "athena", "redshift", "databricks". Non-SQL types (airtable, google-analytics, kintone, wix-store, dbt) should be used via TypeScript handlers.`
234
421
  );
235
422
  }
236
423
  function reloadEnvFile2(envPath) {
@@ -254,19 +441,18 @@ function createConnectorRegistry() {
254
441
  const envPath = path.join(process.cwd(), ".env");
255
442
  try {
256
443
  fsWatch(filePath, { persistent: false }, () => {
257
- console.log("[connector-client] connections.json changed, clearing cache");
258
- connectionsCache = null;
444
+ console.log("[connector-client] connections.json changed, clearing client cache");
259
445
  clientCache.clear();
260
446
  setImmediate(() => reloadEnvFile2(envPath));
261
447
  });
262
448
  } catch {
263
449
  }
264
450
  }
265
- return { getClient: getClient2, reloadEnvFile: reloadEnvFile2, watchConnectionsFile: watchConnectionsFile2 };
451
+ return { getClient: getClient2, loadConnections: loadConnections2, reloadEnvFile: reloadEnvFile2, watchConnectionsFile: watchConnectionsFile2 };
266
452
  }
267
453
 
268
454
  // src/connector-client/index.ts
269
- var { getClient, reloadEnvFile, watchConnectionsFile } = createConnectorRegistry();
455
+ var { getClient, loadConnections, reloadEnvFile, watchConnectionsFile } = createConnectorRegistry();
270
456
 
271
457
  // src/cli/env-loader.ts
272
458
  import { readFileSync as readFileSync2 } from "fs";
@@ -276,11 +462,11 @@ function loadEnvFile(envPath) {
276
462
 
277
463
  // src/cli/runner.ts
278
464
  import { pathToFileURL } from "url";
279
- import { readFile as readFile2, readdir as readdir2 } from "fs/promises";
465
+ import { readFile as readFile3, readdir as readdir2 } from "fs/promises";
280
466
  import path3 from "path";
281
467
 
282
468
  // src/registry.ts
283
- import { readdir, readFile, mkdir } from "fs/promises";
469
+ import { readdir, readFile as readFile2, mkdir } from "fs/promises";
284
470
  import { watch as fsWatch2 } from "fs";
285
471
  import path2 from "path";
286
472
  function buildQuery(queryTemplate, parameterMeta, runtimeParams) {
@@ -337,10 +523,10 @@ async function runSqlDataSource(slug, def, params, limit) {
337
523
  const start = Date.now();
338
524
  try {
339
525
  const { client, connectorSlug } = await getClient(def.connectionId);
340
- const isExternal = connectorSlug === "snowflake" || connectorSlug === "bigquery";
526
+ const isLiteralConnector = connectorSlug === "snowflake" || connectorSlug === "bigquery" || connectorSlug === "athena" || connectorSlug === "redshift" || connectorSlug === "databricks";
341
527
  let queryText;
342
528
  let queryValues;
343
- if (isExternal) {
529
+ if (isLiteralConnector) {
344
530
  const defaults = new Map(
345
531
  (def.parameters ?? []).map((p) => [p.name, p.default ?? null])
346
532
  );
@@ -351,6 +537,10 @@ async function runSqlDataSource(slug, def, params, limit) {
351
537
  return String(value);
352
538
  });
353
539
  queryValues = [];
540
+ } else if (connectorSlug === "mysql") {
541
+ const built = buildQuery(def.query, def.parameters ?? [], params);
542
+ queryText = built.text.replace(/\$(\d+)/g, "?");
543
+ queryValues = built.values;
354
544
  } else {
355
545
  const built = buildQuery(def.query, def.parameters ?? [], params);
356
546
  queryText = built.text;
@@ -414,7 +604,7 @@ async function runDataSource(slug, dirPath, params, limit) {
414
604
  const jsonPath = path3.join(dirPath, `${slug}.json`);
415
605
  let def;
416
606
  try {
417
- const raw = await readFile2(jsonPath, "utf-8");
607
+ const raw = await readFile3(jsonPath, "utf-8");
418
608
  def = JSON.parse(raw);
419
609
  } catch {
420
610
  return {
@@ -620,7 +810,7 @@ Total: ${results.length}, Failed: ${failed}`);
620
810
  const jsonPath = path4.join(dirPath, `${slug}.json`);
621
811
  let paramMeta = [];
622
812
  try {
623
- const raw = await readFile3(jsonPath, "utf-8");
813
+ const raw = await readFile4(jsonPath, "utf-8");
624
814
  const def = JSON.parse(raw);
625
815
  paramMeta = def.parameters ?? [];
626
816
  } catch {
package/dist/index.d.ts CHANGED
@@ -1,6 +1,131 @@
1
1
  import * as hono_types from 'hono/types';
2
2
  import { Hono } from 'hono';
3
3
 
4
+ interface DatabaseClient {
5
+ query(sql: string, params?: unknown[]): Promise<{
6
+ rows: Record<string, unknown>[];
7
+ }>;
8
+ }
9
+ interface ConnectionEntry {
10
+ connector: {
11
+ slug: string;
12
+ authType?: string | null;
13
+ };
14
+ envVars: Record<string, string>;
15
+ }
16
+ type ConnectionsMap = Record<string, ConnectionEntry>;
17
+
18
+ interface AirtableClient {
19
+ listRecords(tableIdOrName: string, options?: {
20
+ fields?: string[];
21
+ filterByFormula?: string;
22
+ maxRecords?: number;
23
+ sort?: {
24
+ field: string;
25
+ direction?: "asc" | "desc";
26
+ }[];
27
+ pageSize?: number;
28
+ offset?: string;
29
+ }): Promise<{
30
+ records: AirtableRecord[];
31
+ offset?: string;
32
+ }>;
33
+ getRecord(tableIdOrName: string, recordId: string): Promise<AirtableRecord>;
34
+ }
35
+ interface AirtableRecord {
36
+ id: string;
37
+ fields: Record<string, unknown>;
38
+ createdTime: string;
39
+ }
40
+ declare function createAirtableClient(entry: ConnectionEntry, slug: string): AirtableClient;
41
+
42
+ interface GoogleAnalyticsClient {
43
+ runReport(request: {
44
+ dateRanges: {
45
+ startDate: string;
46
+ endDate: string;
47
+ }[];
48
+ dimensions?: {
49
+ name: string;
50
+ }[];
51
+ metrics: {
52
+ name: string;
53
+ }[];
54
+ limit?: number;
55
+ offset?: number;
56
+ orderBys?: unknown[];
57
+ }): Promise<{
58
+ rows: {
59
+ dimensionValues: {
60
+ value: string;
61
+ }[];
62
+ metricValues: {
63
+ value: string;
64
+ }[];
65
+ }[];
66
+ rowCount: number;
67
+ }>;
68
+ }
69
+ declare function createGoogleAnalyticsClient(entry: ConnectionEntry, slug: string): GoogleAnalyticsClient;
70
+
71
+ interface KintoneClient {
72
+ getRecords(appId: string | number, options?: {
73
+ query?: string;
74
+ fields?: string[];
75
+ totalCount?: boolean;
76
+ }): Promise<{
77
+ records: Record<string, unknown>[];
78
+ totalCount: string | null;
79
+ }>;
80
+ getRecord(appId: string | number, recordId: string | number): Promise<{
81
+ record: Record<string, unknown>;
82
+ }>;
83
+ listApps(options?: {
84
+ ids?: (string | number)[];
85
+ name?: string;
86
+ limit?: number;
87
+ offset?: number;
88
+ }): Promise<{
89
+ apps: Record<string, unknown>[];
90
+ }>;
91
+ }
92
+ declare function createKintoneClient(entry: ConnectionEntry, slug: string): KintoneClient;
93
+
94
+ interface WixStoreClient {
95
+ queryProducts(options?: {
96
+ query?: Record<string, unknown>;
97
+ limit?: number;
98
+ offset?: number;
99
+ }): Promise<{
100
+ products: Record<string, unknown>[];
101
+ totalResults: number;
102
+ }>;
103
+ queryOrders(options?: {
104
+ query?: Record<string, unknown>;
105
+ limit?: number;
106
+ offset?: number;
107
+ }): Promise<{
108
+ orders: Record<string, unknown>[];
109
+ totalResults: number;
110
+ }>;
111
+ }
112
+ declare function createWixStoreClient(entry: ConnectionEntry, slug: string): WixStoreClient;
113
+
114
+ interface DbtClient {
115
+ query(graphqlQuery: string, variables?: Record<string, unknown>): Promise<Record<string, unknown>>;
116
+ getModels(options?: {
117
+ limit?: number;
118
+ }): Promise<Record<string, unknown>[]>;
119
+ getModelByName(uniqueId: string): Promise<Record<string, unknown> | null>;
120
+ }
121
+ declare function createDbtClient(entry: ConnectionEntry, slug: string): DbtClient;
122
+
123
+ declare const getClient: (connectionId: string) => Promise<{
124
+ client: DatabaseClient;
125
+ connectorSlug: string;
126
+ }>;
127
+ declare const loadConnections: () => Promise<ConnectionsMap>;
128
+
4
129
  declare const app: Hono<hono_types.BlankEnv, hono_types.BlankSchema, "/">;
5
130
 
6
- export { app as default };
131
+ export { type AirtableClient, type AirtableRecord, type ConnectionEntry, type ConnectionsMap, type DatabaseClient, type DbtClient, type GoogleAnalyticsClient, type KintoneClient, type WixStoreClient, createAirtableClient, createDbtClient, createGoogleAnalyticsClient, createKintoneClient, createWixStoreClient, app as default, getClient, loadConnections };