@squadbase/vite-server 0.0.1-build-7 → 0.0.1-build-8
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 +201 -8
- package/dist/index.d.ts +126 -1
- package/dist/index.js +475 -9
- package/dist/main.js +205 -8
- package/dist/types/data-source.d.ts +5 -5
- package/dist/vite-plugin.js +209 -7
- package/package.json +8 -2
package/dist/cli/index.js
CHANGED
|
@@ -113,6 +113,11 @@ function resolveEnvVar(entry, key, connectionId) {
|
|
|
113
113
|
}
|
|
114
114
|
return value;
|
|
115
115
|
}
|
|
116
|
+
function resolveEnvVarOptional(entry, key) {
|
|
117
|
+
const envVarName = entry.envVars[key];
|
|
118
|
+
if (!envVarName) return void 0;
|
|
119
|
+
return process.env[envVarName] || void 0;
|
|
120
|
+
}
|
|
116
121
|
|
|
117
122
|
// src/connector-client/bigquery.ts
|
|
118
123
|
function createBigQueryClient(entry, connectionId) {
|
|
@@ -124,7 +129,7 @@ function createBigQueryClient(entry, connectionId) {
|
|
|
124
129
|
gcpCredentials = JSON.parse(serviceAccountJson);
|
|
125
130
|
} catch {
|
|
126
131
|
throw new Error(
|
|
127
|
-
`BigQuery service account JSON (decoded from base64) is not valid JSON for
|
|
132
|
+
`BigQuery service account JSON (decoded from base64) is not valid JSON for connectionId "${connectionId}"`
|
|
128
133
|
);
|
|
129
134
|
}
|
|
130
135
|
return {
|
|
@@ -181,6 +186,176 @@ function createSnowflakeClient(entry, connectionId) {
|
|
|
181
186
|
};
|
|
182
187
|
}
|
|
183
188
|
|
|
189
|
+
// src/connector-client/mysql.ts
|
|
190
|
+
function createMySQLClient(entry, connectionId) {
|
|
191
|
+
const connectionUrl = resolveEnvVar(entry, "connection-url", connectionId);
|
|
192
|
+
let poolPromise = null;
|
|
193
|
+
function getPool() {
|
|
194
|
+
if (!poolPromise) {
|
|
195
|
+
poolPromise = import("mysql2/promise").then(
|
|
196
|
+
(mysql) => mysql.default.createPool(connectionUrl)
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
return poolPromise;
|
|
200
|
+
}
|
|
201
|
+
return {
|
|
202
|
+
async query(sql, params) {
|
|
203
|
+
const pool = await getPool();
|
|
204
|
+
const [rows] = await pool.execute(sql, params);
|
|
205
|
+
return { rows };
|
|
206
|
+
}
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// src/connector-client/aws-athena.ts
|
|
211
|
+
function createAthenaClient(entry, connectionId) {
|
|
212
|
+
const region = resolveEnvVar(entry, "aws-region", connectionId);
|
|
213
|
+
const accessKeyId = resolveEnvVar(entry, "aws-access-key-id", connectionId);
|
|
214
|
+
const secretAccessKey = resolveEnvVar(entry, "aws-secret-access-key", connectionId);
|
|
215
|
+
const workgroup = resolveEnvVarOptional(entry, "workgroup") ?? "primary";
|
|
216
|
+
const outputLocation = resolveEnvVarOptional(entry, "output-location");
|
|
217
|
+
return {
|
|
218
|
+
async query(sql) {
|
|
219
|
+
const {
|
|
220
|
+
AthenaClient,
|
|
221
|
+
StartQueryExecutionCommand,
|
|
222
|
+
GetQueryExecutionCommand,
|
|
223
|
+
GetQueryResultsCommand
|
|
224
|
+
} = await import("@aws-sdk/client-athena");
|
|
225
|
+
const client = new AthenaClient({
|
|
226
|
+
region,
|
|
227
|
+
credentials: { accessKeyId, secretAccessKey }
|
|
228
|
+
});
|
|
229
|
+
const startParams = {
|
|
230
|
+
QueryString: sql,
|
|
231
|
+
WorkGroup: workgroup
|
|
232
|
+
};
|
|
233
|
+
if (outputLocation) {
|
|
234
|
+
startParams.ResultConfiguration = { OutputLocation: outputLocation };
|
|
235
|
+
}
|
|
236
|
+
const { QueryExecutionId } = await client.send(
|
|
237
|
+
new StartQueryExecutionCommand(startParams)
|
|
238
|
+
);
|
|
239
|
+
if (!QueryExecutionId) throw new Error("Athena: failed to start query execution");
|
|
240
|
+
while (true) {
|
|
241
|
+
const { QueryExecution } = await client.send(
|
|
242
|
+
new GetQueryExecutionCommand({ QueryExecutionId })
|
|
243
|
+
);
|
|
244
|
+
const state = QueryExecution?.Status?.State;
|
|
245
|
+
if (state === "SUCCEEDED") break;
|
|
246
|
+
if (state === "FAILED") {
|
|
247
|
+
throw new Error(
|
|
248
|
+
`Athena query failed: ${QueryExecution?.Status?.StateChangeReason ?? "unknown"}`
|
|
249
|
+
);
|
|
250
|
+
}
|
|
251
|
+
if (state === "CANCELLED") throw new Error("Athena query was cancelled");
|
|
252
|
+
await new Promise((r) => setTimeout(r, 500));
|
|
253
|
+
}
|
|
254
|
+
const { ResultSet } = await client.send(
|
|
255
|
+
new GetQueryResultsCommand({ QueryExecutionId })
|
|
256
|
+
);
|
|
257
|
+
const resultRows = ResultSet?.Rows ?? [];
|
|
258
|
+
if (resultRows.length === 0) return { rows: [] };
|
|
259
|
+
const headers = resultRows[0].Data?.map((d) => d.VarCharValue ?? "") ?? [];
|
|
260
|
+
const rows = resultRows.slice(1).map((row) => {
|
|
261
|
+
const obj = {};
|
|
262
|
+
row.Data?.forEach((d, i) => {
|
|
263
|
+
obj[headers[i]] = d.VarCharValue ?? null;
|
|
264
|
+
});
|
|
265
|
+
return obj;
|
|
266
|
+
});
|
|
267
|
+
return { rows };
|
|
268
|
+
}
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// src/connector-client/redshift.ts
|
|
273
|
+
function createRedshiftClient(entry, connectionId) {
|
|
274
|
+
const region = resolveEnvVar(entry, "aws-region", connectionId);
|
|
275
|
+
const accessKeyId = resolveEnvVar(entry, "aws-access-key-id", connectionId);
|
|
276
|
+
const secretAccessKey = resolveEnvVar(entry, "aws-secret-access-key", connectionId);
|
|
277
|
+
const database = resolveEnvVar(entry, "database", connectionId);
|
|
278
|
+
const clusterIdentifier = resolveEnvVarOptional(entry, "cluster-identifier");
|
|
279
|
+
const workgroupName = resolveEnvVarOptional(entry, "workgroup-name");
|
|
280
|
+
const secretArn = resolveEnvVarOptional(entry, "secret-arn");
|
|
281
|
+
const dbUser = resolveEnvVarOptional(entry, "db-user");
|
|
282
|
+
return {
|
|
283
|
+
async query(sql) {
|
|
284
|
+
const {
|
|
285
|
+
RedshiftDataClient,
|
|
286
|
+
ExecuteStatementCommand,
|
|
287
|
+
DescribeStatementCommand,
|
|
288
|
+
GetStatementResultCommand
|
|
289
|
+
} = await import("@aws-sdk/client-redshift-data");
|
|
290
|
+
const client = new RedshiftDataClient({
|
|
291
|
+
region,
|
|
292
|
+
credentials: { accessKeyId, secretAccessKey }
|
|
293
|
+
});
|
|
294
|
+
const executeParams = {
|
|
295
|
+
Sql: sql,
|
|
296
|
+
Database: database
|
|
297
|
+
};
|
|
298
|
+
if (clusterIdentifier) executeParams.ClusterIdentifier = clusterIdentifier;
|
|
299
|
+
if (workgroupName) executeParams.WorkgroupName = workgroupName;
|
|
300
|
+
if (secretArn) executeParams.SecretArn = secretArn;
|
|
301
|
+
if (dbUser) executeParams.DbUser = dbUser;
|
|
302
|
+
const { Id } = await client.send(
|
|
303
|
+
new ExecuteStatementCommand(executeParams)
|
|
304
|
+
);
|
|
305
|
+
if (!Id) throw new Error("Redshift: failed to start statement execution");
|
|
306
|
+
while (true) {
|
|
307
|
+
const desc = await client.send(new DescribeStatementCommand({ Id }));
|
|
308
|
+
const status = desc.Status;
|
|
309
|
+
if (status === "FINISHED") break;
|
|
310
|
+
if (status === "FAILED") {
|
|
311
|
+
throw new Error(`Redshift query failed: ${desc.Error ?? "unknown"}`);
|
|
312
|
+
}
|
|
313
|
+
if (status === "ABORTED") throw new Error("Redshift query was aborted");
|
|
314
|
+
await new Promise((r) => setTimeout(r, 500));
|
|
315
|
+
}
|
|
316
|
+
const result = await client.send(new GetStatementResultCommand({ Id }));
|
|
317
|
+
const columns = result.ColumnMetadata?.map((c) => c.name ?? "") ?? [];
|
|
318
|
+
const rows = (result.Records ?? []).map((record) => {
|
|
319
|
+
const obj = {};
|
|
320
|
+
record.forEach((field, i) => {
|
|
321
|
+
const col = columns[i];
|
|
322
|
+
const value = field.stringValue ?? field.longValue ?? field.doubleValue ?? field.booleanValue ?? (field.isNull ? null : field.blobValue ?? null);
|
|
323
|
+
obj[col] = value;
|
|
324
|
+
});
|
|
325
|
+
return obj;
|
|
326
|
+
});
|
|
327
|
+
return { rows };
|
|
328
|
+
}
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// src/connector-client/databricks.ts
|
|
333
|
+
function createDatabricksClient(entry, connectionId) {
|
|
334
|
+
const host = resolveEnvVar(entry, "host", connectionId);
|
|
335
|
+
const httpPath = resolveEnvVar(entry, "http-path", connectionId);
|
|
336
|
+
const token = resolveEnvVar(entry, "token", connectionId);
|
|
337
|
+
return {
|
|
338
|
+
async query(sql) {
|
|
339
|
+
const { DBSQLClient } = await import("@databricks/sql");
|
|
340
|
+
const client = new DBSQLClient();
|
|
341
|
+
await client.connect({ host, path: httpPath, token });
|
|
342
|
+
try {
|
|
343
|
+
const session = await client.openSession();
|
|
344
|
+
try {
|
|
345
|
+
const operation = await session.executeStatement(sql);
|
|
346
|
+
const result = await operation.fetchAll();
|
|
347
|
+
await operation.close();
|
|
348
|
+
return { rows: result };
|
|
349
|
+
} finally {
|
|
350
|
+
await session.close();
|
|
351
|
+
}
|
|
352
|
+
} finally {
|
|
353
|
+
await client.close();
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
|
|
184
359
|
// src/connector-client/registry.ts
|
|
185
360
|
function createConnectorRegistry() {
|
|
186
361
|
let connectionsCache = null;
|
|
@@ -188,7 +363,7 @@ function createConnectorRegistry() {
|
|
|
188
363
|
function getConnectionsFilePath() {
|
|
189
364
|
return process.env.CONNECTIONS_PATH ?? path.join(process.cwd(), ".squadbase/connections.json");
|
|
190
365
|
}
|
|
191
|
-
function
|
|
366
|
+
function loadConnections2() {
|
|
192
367
|
if (connectionsCache !== null) return connectionsCache;
|
|
193
368
|
const filePath = getConnectionsFilePath();
|
|
194
369
|
try {
|
|
@@ -200,7 +375,7 @@ function createConnectorRegistry() {
|
|
|
200
375
|
return connectionsCache;
|
|
201
376
|
}
|
|
202
377
|
async function getClient2(connectionId) {
|
|
203
|
-
const connections =
|
|
378
|
+
const connections = loadConnections2();
|
|
204
379
|
const entry = connections[connectionId];
|
|
205
380
|
if (!entry) {
|
|
206
381
|
throw new Error(`connection '${connectionId}' not found in .squadbase/connections.json`);
|
|
@@ -214,6 +389,20 @@ function createConnectorRegistry() {
|
|
|
214
389
|
if (connectorSlug === "bigquery") {
|
|
215
390
|
return { client: createBigQueryClient(entry, connectionId), connectorSlug };
|
|
216
391
|
}
|
|
392
|
+
if (connectorSlug === "athena") {
|
|
393
|
+
return { client: createAthenaClient(entry, connectionId), connectorSlug };
|
|
394
|
+
}
|
|
395
|
+
if (connectorSlug === "redshift") {
|
|
396
|
+
return { client: createRedshiftClient(entry, connectionId), connectorSlug };
|
|
397
|
+
}
|
|
398
|
+
if (connectorSlug === "databricks") {
|
|
399
|
+
return { client: createDatabricksClient(entry, connectionId), connectorSlug };
|
|
400
|
+
}
|
|
401
|
+
if (connectorSlug === "mysql") {
|
|
402
|
+
const client = createMySQLClient(entry, connectionId);
|
|
403
|
+
clientCache.set(connectionId, client);
|
|
404
|
+
return { client, connectorSlug };
|
|
405
|
+
}
|
|
217
406
|
if (connectorSlug === "postgresql" || connectorSlug === "squadbase-db") {
|
|
218
407
|
const urlEnvName = entry.envVars["connection-url"];
|
|
219
408
|
if (!urlEnvName) {
|
|
@@ -230,7 +419,7 @@ function createConnectorRegistry() {
|
|
|
230
419
|
return { client, connectorSlug };
|
|
231
420
|
}
|
|
232
421
|
throw new Error(
|
|
233
|
-
`connector type '${connectorSlug}' is not supported. Supported: "snowflake", "bigquery", "
|
|
422
|
+
`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
423
|
);
|
|
235
424
|
}
|
|
236
425
|
function reloadEnvFile2(envPath) {
|
|
@@ -262,11 +451,11 @@ function createConnectorRegistry() {
|
|
|
262
451
|
} catch {
|
|
263
452
|
}
|
|
264
453
|
}
|
|
265
|
-
return { getClient: getClient2, reloadEnvFile: reloadEnvFile2, watchConnectionsFile: watchConnectionsFile2 };
|
|
454
|
+
return { getClient: getClient2, loadConnections: loadConnections2, reloadEnvFile: reloadEnvFile2, watchConnectionsFile: watchConnectionsFile2 };
|
|
266
455
|
}
|
|
267
456
|
|
|
268
457
|
// src/connector-client/index.ts
|
|
269
|
-
var { getClient, reloadEnvFile, watchConnectionsFile } = createConnectorRegistry();
|
|
458
|
+
var { getClient, loadConnections, reloadEnvFile, watchConnectionsFile } = createConnectorRegistry();
|
|
270
459
|
|
|
271
460
|
// src/cli/env-loader.ts
|
|
272
461
|
import { readFileSync as readFileSync2 } from "fs";
|
|
@@ -337,10 +526,10 @@ async function runSqlDataSource(slug, def, params, limit) {
|
|
|
337
526
|
const start = Date.now();
|
|
338
527
|
try {
|
|
339
528
|
const { client, connectorSlug } = await getClient(def.connectionId);
|
|
340
|
-
const
|
|
529
|
+
const isLiteralConnector = connectorSlug === "snowflake" || connectorSlug === "bigquery" || connectorSlug === "athena" || connectorSlug === "redshift" || connectorSlug === "databricks";
|
|
341
530
|
let queryText;
|
|
342
531
|
let queryValues;
|
|
343
|
-
if (
|
|
532
|
+
if (isLiteralConnector) {
|
|
344
533
|
const defaults = new Map(
|
|
345
534
|
(def.parameters ?? []).map((p) => [p.name, p.default ?? null])
|
|
346
535
|
);
|
|
@@ -351,6 +540,10 @@ async function runSqlDataSource(slug, def, params, limit) {
|
|
|
351
540
|
return String(value);
|
|
352
541
|
});
|
|
353
542
|
queryValues = [];
|
|
543
|
+
} else if (connectorSlug === "mysql") {
|
|
544
|
+
const built = buildQuery(def.query, def.parameters ?? [], params);
|
|
545
|
+
queryText = built.text.replace(/\$(\d+)/g, "?");
|
|
546
|
+
queryValues = built.values;
|
|
354
547
|
} else {
|
|
355
548
|
const built = buildQuery(def.query, def.parameters ?? [], params);
|
|
356
549
|
queryText = built.text;
|
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: () => 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 };
|