@squadbase/vite-server 0.0.1-build-13 → 0.0.1-build-15
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 +48 -530
- package/dist/index.d.ts +6 -10
- package/dist/index.js +120 -544
- package/dist/main.js +54 -543
- package/dist/vite-plugin.js +34 -489
- package/package.json +2 -1
package/dist/index.js
CHANGED
|
@@ -13,19 +13,7 @@ import path2 from "path";
|
|
|
13
13
|
import { readFileSync, watch as fsWatch } from "fs";
|
|
14
14
|
import { readFile } from "fs/promises";
|
|
15
15
|
import path from "path";
|
|
16
|
-
|
|
17
|
-
// src/connector-client/postgresql.ts
|
|
18
|
-
import pg from "pg";
|
|
19
|
-
var { Pool } = pg;
|
|
20
|
-
function createPostgreSQLClient(connectionString) {
|
|
21
|
-
const pool = new Pool({ connectionString, ssl: { rejectUnauthorized: false } });
|
|
22
|
-
return {
|
|
23
|
-
async query(sql, params) {
|
|
24
|
-
const result = await pool.query(sql, params);
|
|
25
|
-
return { rows: result.rows };
|
|
26
|
-
}
|
|
27
|
-
};
|
|
28
|
-
}
|
|
16
|
+
import { connectors } from "@squadbase/connectors";
|
|
29
17
|
|
|
30
18
|
// src/connector-client/env.ts
|
|
31
19
|
function resolveEnvVar(entry, key, connectionId) {
|
|
@@ -45,433 +33,8 @@ function resolveEnvVarOptional(entry, key) {
|
|
|
45
33
|
return process.env[envVarName] || void 0;
|
|
46
34
|
}
|
|
47
35
|
|
|
48
|
-
// src/connector-client/bigquery.ts
|
|
49
|
-
function createBigQueryClient(entry, connectionId) {
|
|
50
|
-
const projectId = resolveEnvVar(entry, "project-id", connectionId);
|
|
51
|
-
const serviceAccountJsonBase64 = resolveEnvVar(entry, "service-account-key-json-base64", connectionId);
|
|
52
|
-
const serviceAccountJson = Buffer.from(serviceAccountJsonBase64, "base64").toString("utf-8");
|
|
53
|
-
let gcpCredentials;
|
|
54
|
-
try {
|
|
55
|
-
gcpCredentials = JSON.parse(serviceAccountJson);
|
|
56
|
-
} catch {
|
|
57
|
-
throw new Error(
|
|
58
|
-
`BigQuery service account JSON (decoded from base64) is not valid JSON for connectionId "${connectionId}"`
|
|
59
|
-
);
|
|
60
|
-
}
|
|
61
|
-
return {
|
|
62
|
-
async query(sql) {
|
|
63
|
-
const { BigQuery } = await import("@google-cloud/bigquery");
|
|
64
|
-
const bq = new BigQuery({ projectId, credentials: gcpCredentials });
|
|
65
|
-
const [job] = await bq.createQueryJob({ query: sql });
|
|
66
|
-
const [allRows] = await job.getQueryResults({ timeoutMs: 3e4 });
|
|
67
|
-
return { rows: allRows };
|
|
68
|
-
}
|
|
69
|
-
};
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
// src/connection.ts
|
|
73
|
-
import { getContext } from "hono/context-storage";
|
|
74
|
-
import { getCookie } from "hono/cookie";
|
|
75
|
-
var APP_SESSION_COOKIE_NAME = "__Host-squadbase-session";
|
|
76
|
-
var PREVIEW_SESSION_COOKIE_NAME = "squadbase-preview-session";
|
|
77
|
-
var APP_BASE_DOMAIN = "squadbase.app";
|
|
78
|
-
var PREVIEW_BASE_DOMAIN = "preview.app.squadbase.dev";
|
|
79
|
-
var SANDBOX_ID_ENV_NAME = "INTERNAL_SQUADBASE_SANDBOX_ID";
|
|
80
|
-
var MACHINE_CREDENTIAL_ENV_NAME = "INTERNAL_SQUADBASE_OAUTH_MACHINE_CREDENTIAL";
|
|
81
|
-
function resolveProxyUrl(connectionId) {
|
|
82
|
-
const connectionPath = `/_sqcore/connections/${connectionId}/request`;
|
|
83
|
-
const sandboxId = process.env[SANDBOX_ID_ENV_NAME];
|
|
84
|
-
if (sandboxId) {
|
|
85
|
-
const baseDomain2 = process.env["SQUADBASE_PREVIEW_BASE_DOMAIN"] ?? PREVIEW_BASE_DOMAIN;
|
|
86
|
-
return `https://${sandboxId}.${baseDomain2}${connectionPath}`;
|
|
87
|
-
}
|
|
88
|
-
const projectId = process.env["SQUADBASE_PROJECT_ID"];
|
|
89
|
-
if (!projectId) {
|
|
90
|
-
throw new Error(
|
|
91
|
-
"Project ID is required. Please set SQUADBASE_PROJECT_ID environment variable."
|
|
92
|
-
);
|
|
93
|
-
}
|
|
94
|
-
const baseDomain = process.env["SQUADBASE_APP_BASE_DOMAIN"] ?? APP_BASE_DOMAIN;
|
|
95
|
-
return `https://${projectId}.${baseDomain}${connectionPath}`;
|
|
96
|
-
}
|
|
97
|
-
function resolveAuthHeaders() {
|
|
98
|
-
const machineCredential = process.env[MACHINE_CREDENTIAL_ENV_NAME];
|
|
99
|
-
if (machineCredential) {
|
|
100
|
-
return { Authorization: `Bearer ${machineCredential}` };
|
|
101
|
-
}
|
|
102
|
-
const c = getContext();
|
|
103
|
-
const cookies = getCookie(c);
|
|
104
|
-
const previewSession = cookies[PREVIEW_SESSION_COOKIE_NAME];
|
|
105
|
-
if (previewSession) {
|
|
106
|
-
return {
|
|
107
|
-
Cookie: `${PREVIEW_SESSION_COOKIE_NAME}=${previewSession}`
|
|
108
|
-
};
|
|
109
|
-
}
|
|
110
|
-
const appSession = cookies[APP_SESSION_COOKIE_NAME];
|
|
111
|
-
if (appSession) {
|
|
112
|
-
return { Authorization: `Bearer ${appSession}` };
|
|
113
|
-
}
|
|
114
|
-
throw new Error(
|
|
115
|
-
"No authentication method available for connection proxy. Expected one of: INTERNAL_SQUADBASE_OAUTH_MACHINE_CREDENTIAL env var, preview session cookie, or app session cookie."
|
|
116
|
-
);
|
|
117
|
-
}
|
|
118
|
-
function connection(connectionId) {
|
|
119
|
-
return {
|
|
120
|
-
async fetch(url, options) {
|
|
121
|
-
const proxyUrl = resolveProxyUrl(connectionId);
|
|
122
|
-
const authHeaders = resolveAuthHeaders();
|
|
123
|
-
return await fetch(proxyUrl, {
|
|
124
|
-
method: "POST",
|
|
125
|
-
headers: {
|
|
126
|
-
"Content-Type": "application/json",
|
|
127
|
-
...authHeaders
|
|
128
|
-
},
|
|
129
|
-
body: JSON.stringify({
|
|
130
|
-
url,
|
|
131
|
-
method: options?.method,
|
|
132
|
-
headers: options?.headers,
|
|
133
|
-
body: options?.body,
|
|
134
|
-
timeoutMs: options?.timeoutMs
|
|
135
|
-
})
|
|
136
|
-
});
|
|
137
|
-
}
|
|
138
|
-
};
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
// src/connector-client/bigquery-oauth.ts
|
|
142
|
-
var MAX_RESULTS = 1e4;
|
|
143
|
-
var POLL_INTERVAL_MS = 1e3;
|
|
144
|
-
var POLL_TIMEOUT_MS = 12e4;
|
|
145
|
-
function flattenRows(fields, rows) {
|
|
146
|
-
return rows.map((row) => {
|
|
147
|
-
const obj = {};
|
|
148
|
-
for (let i = 0; i < fields.length; i++) {
|
|
149
|
-
obj[fields[i].name] = row.f[i].v;
|
|
150
|
-
}
|
|
151
|
-
return obj;
|
|
152
|
-
});
|
|
153
|
-
}
|
|
154
|
-
function createBigQueryOAuthClient(entry, connectionId) {
|
|
155
|
-
const projectId = resolveEnvVar(entry, "project-id", connectionId);
|
|
156
|
-
const baseUrl = `https://bigquery.googleapis.com/bigquery/v2/projects/${projectId}`;
|
|
157
|
-
return {
|
|
158
|
-
async query(sql) {
|
|
159
|
-
const conn = connection(connectionId);
|
|
160
|
-
const res = await conn.fetch(`${baseUrl}/queries`, {
|
|
161
|
-
method: "POST",
|
|
162
|
-
body: {
|
|
163
|
-
query: sql,
|
|
164
|
-
useLegacySql: false,
|
|
165
|
-
maxResults: MAX_RESULTS
|
|
166
|
-
}
|
|
167
|
-
});
|
|
168
|
-
if (!res.ok) {
|
|
169
|
-
const text = await res.text().catch(() => res.statusText);
|
|
170
|
-
throw new Error(`BigQuery query failed: HTTP ${res.status} ${text}`);
|
|
171
|
-
}
|
|
172
|
-
let data = await res.json();
|
|
173
|
-
if (data.errors?.length) {
|
|
174
|
-
throw new Error(
|
|
175
|
-
`BigQuery query error: ${data.errors.map((e) => e.message).join("; ")}`
|
|
176
|
-
);
|
|
177
|
-
}
|
|
178
|
-
if (!data.jobComplete) {
|
|
179
|
-
const jobId = data.jobReference.jobId;
|
|
180
|
-
const location = data.jobReference.location;
|
|
181
|
-
const deadline = Date.now() + POLL_TIMEOUT_MS;
|
|
182
|
-
while (!data.jobComplete) {
|
|
183
|
-
if (Date.now() > deadline) {
|
|
184
|
-
throw new Error(
|
|
185
|
-
`BigQuery query timed out after ${POLL_TIMEOUT_MS / 1e3}s (jobId: ${jobId})`
|
|
186
|
-
);
|
|
187
|
-
}
|
|
188
|
-
await new Promise((r) => setTimeout(r, POLL_INTERVAL_MS));
|
|
189
|
-
const params = new URLSearchParams({
|
|
190
|
-
maxResults: String(MAX_RESULTS)
|
|
191
|
-
});
|
|
192
|
-
if (location) params.set("location", location);
|
|
193
|
-
const pollRes = await conn.fetch(
|
|
194
|
-
`${baseUrl}/queries/${jobId}?${params}`,
|
|
195
|
-
{ method: "GET" }
|
|
196
|
-
);
|
|
197
|
-
if (!pollRes.ok) {
|
|
198
|
-
const text = await pollRes.text().catch(() => pollRes.statusText);
|
|
199
|
-
throw new Error(
|
|
200
|
-
`BigQuery poll failed: HTTP ${pollRes.status} ${text}`
|
|
201
|
-
);
|
|
202
|
-
}
|
|
203
|
-
data = await pollRes.json();
|
|
204
|
-
if (data.errors?.length) {
|
|
205
|
-
throw new Error(
|
|
206
|
-
`BigQuery query error: ${data.errors.map((e) => e.message).join("; ")}`
|
|
207
|
-
);
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
const fields = data.schema?.fields ?? [];
|
|
212
|
-
const rawRows = data.rows ?? [];
|
|
213
|
-
return { rows: flattenRows(fields, rawRows) };
|
|
214
|
-
}
|
|
215
|
-
};
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
// src/connector-client/snowflake.ts
|
|
219
|
-
function createSnowflakeClient(entry, connectionId) {
|
|
220
|
-
const accountIdentifier = resolveEnvVar(entry, "account", connectionId);
|
|
221
|
-
const user = resolveEnvVar(entry, "user", connectionId);
|
|
222
|
-
const role = resolveEnvVar(entry, "role", connectionId);
|
|
223
|
-
const warehouse = resolveEnvVar(entry, "warehouse", connectionId);
|
|
224
|
-
const privateKeyBase64 = resolveEnvVar(entry, "private-key-base64", connectionId);
|
|
225
|
-
const privateKey = Buffer.from(privateKeyBase64, "base64").toString("utf-8");
|
|
226
|
-
return {
|
|
227
|
-
async query(sql) {
|
|
228
|
-
const snowflake = (await import("snowflake-sdk")).default;
|
|
229
|
-
snowflake.configure({ logLevel: "ERROR" });
|
|
230
|
-
const connection2 = snowflake.createConnection({
|
|
231
|
-
account: accountIdentifier,
|
|
232
|
-
username: user,
|
|
233
|
-
role,
|
|
234
|
-
warehouse,
|
|
235
|
-
authenticator: "SNOWFLAKE_JWT",
|
|
236
|
-
privateKey
|
|
237
|
-
});
|
|
238
|
-
await new Promise((resolve, reject) => {
|
|
239
|
-
connection2.connect((err) => {
|
|
240
|
-
if (err) reject(new Error(`Snowflake connect failed: ${err.message}`));
|
|
241
|
-
else resolve();
|
|
242
|
-
});
|
|
243
|
-
});
|
|
244
|
-
const rows = await new Promise((resolve, reject) => {
|
|
245
|
-
connection2.execute({
|
|
246
|
-
sqlText: sql,
|
|
247
|
-
complete: (err, _stmt, rows2) => {
|
|
248
|
-
if (err) reject(new Error(`Snowflake query failed: ${err.message}`));
|
|
249
|
-
else resolve(rows2 ?? []);
|
|
250
|
-
}
|
|
251
|
-
});
|
|
252
|
-
});
|
|
253
|
-
connection2.destroy((err) => {
|
|
254
|
-
if (err) console.warn(`[connector-client] Snowflake destroy error: ${err.message}`);
|
|
255
|
-
});
|
|
256
|
-
return { rows };
|
|
257
|
-
}
|
|
258
|
-
};
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
// src/connector-client/snowflake-pat.ts
|
|
262
|
-
function createSnowflakePatClient(entry, connectionId) {
|
|
263
|
-
const accountIdentifier = resolveEnvVar(entry, "account", connectionId);
|
|
264
|
-
const user = resolveEnvVar(entry, "user", connectionId);
|
|
265
|
-
const role = resolveEnvVar(entry, "role", connectionId);
|
|
266
|
-
const warehouse = resolveEnvVar(entry, "warehouse", connectionId);
|
|
267
|
-
const password = resolveEnvVar(entry, "pat", connectionId);
|
|
268
|
-
return {
|
|
269
|
-
async query(sql) {
|
|
270
|
-
const snowflake = (await import("snowflake-sdk")).default;
|
|
271
|
-
snowflake.configure({ logLevel: "ERROR" });
|
|
272
|
-
const connection2 = snowflake.createConnection({
|
|
273
|
-
account: accountIdentifier,
|
|
274
|
-
username: user,
|
|
275
|
-
role,
|
|
276
|
-
warehouse,
|
|
277
|
-
password
|
|
278
|
-
});
|
|
279
|
-
await new Promise((resolve, reject) => {
|
|
280
|
-
connection2.connect((err) => {
|
|
281
|
-
if (err) reject(new Error(`Snowflake connect failed: ${err.message}`));
|
|
282
|
-
else resolve();
|
|
283
|
-
});
|
|
284
|
-
});
|
|
285
|
-
const rows = await new Promise((resolve, reject) => {
|
|
286
|
-
connection2.execute({
|
|
287
|
-
sqlText: sql,
|
|
288
|
-
complete: (err, _stmt, rows2) => {
|
|
289
|
-
if (err) reject(new Error(`Snowflake query failed: ${err.message}`));
|
|
290
|
-
else resolve(rows2 ?? []);
|
|
291
|
-
}
|
|
292
|
-
});
|
|
293
|
-
});
|
|
294
|
-
connection2.destroy((err) => {
|
|
295
|
-
if (err) console.warn(`[connector-client] Snowflake destroy error: ${err.message}`);
|
|
296
|
-
});
|
|
297
|
-
return { rows };
|
|
298
|
-
}
|
|
299
|
-
};
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
// src/connector-client/mysql.ts
|
|
303
|
-
function createMySQLClient(entry, connectionId) {
|
|
304
|
-
const connectionUrl = resolveEnvVar(entry, "connection-url", connectionId);
|
|
305
|
-
let poolPromise = null;
|
|
306
|
-
function getPool() {
|
|
307
|
-
if (!poolPromise) {
|
|
308
|
-
poolPromise = import("mysql2/promise").then(
|
|
309
|
-
(mysql) => mysql.default.createPool(connectionUrl)
|
|
310
|
-
);
|
|
311
|
-
}
|
|
312
|
-
return poolPromise;
|
|
313
|
-
}
|
|
314
|
-
return {
|
|
315
|
-
async query(sql, params) {
|
|
316
|
-
const pool = await getPool();
|
|
317
|
-
const [rows] = await pool.execute(sql, params);
|
|
318
|
-
return { rows };
|
|
319
|
-
}
|
|
320
|
-
};
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
// src/connector-client/aws-athena.ts
|
|
324
|
-
function createAthenaClient(entry, connectionId) {
|
|
325
|
-
const region = resolveEnvVar(entry, "aws-region", connectionId);
|
|
326
|
-
const accessKeyId = resolveEnvVar(entry, "aws-access-key-id", connectionId);
|
|
327
|
-
const secretAccessKey = resolveEnvVar(entry, "aws-secret-access-key", connectionId);
|
|
328
|
-
const workgroup = resolveEnvVarOptional(entry, "workgroup") ?? "primary";
|
|
329
|
-
const outputLocation = resolveEnvVarOptional(entry, "output-location");
|
|
330
|
-
return {
|
|
331
|
-
async query(sql) {
|
|
332
|
-
const {
|
|
333
|
-
AthenaClient,
|
|
334
|
-
StartQueryExecutionCommand,
|
|
335
|
-
GetQueryExecutionCommand,
|
|
336
|
-
GetQueryResultsCommand
|
|
337
|
-
} = await import("@aws-sdk/client-athena");
|
|
338
|
-
const client = new AthenaClient({
|
|
339
|
-
region,
|
|
340
|
-
credentials: { accessKeyId, secretAccessKey }
|
|
341
|
-
});
|
|
342
|
-
const startParams = {
|
|
343
|
-
QueryString: sql,
|
|
344
|
-
WorkGroup: workgroup
|
|
345
|
-
};
|
|
346
|
-
if (outputLocation) {
|
|
347
|
-
startParams.ResultConfiguration = { OutputLocation: outputLocation };
|
|
348
|
-
}
|
|
349
|
-
const { QueryExecutionId } = await client.send(
|
|
350
|
-
new StartQueryExecutionCommand(startParams)
|
|
351
|
-
);
|
|
352
|
-
if (!QueryExecutionId) throw new Error("Athena: failed to start query execution");
|
|
353
|
-
while (true) {
|
|
354
|
-
const { QueryExecution } = await client.send(
|
|
355
|
-
new GetQueryExecutionCommand({ QueryExecutionId })
|
|
356
|
-
);
|
|
357
|
-
const state = QueryExecution?.Status?.State;
|
|
358
|
-
if (state === "SUCCEEDED") break;
|
|
359
|
-
if (state === "FAILED") {
|
|
360
|
-
throw new Error(
|
|
361
|
-
`Athena query failed: ${QueryExecution?.Status?.StateChangeReason ?? "unknown"}`
|
|
362
|
-
);
|
|
363
|
-
}
|
|
364
|
-
if (state === "CANCELLED") throw new Error("Athena query was cancelled");
|
|
365
|
-
await new Promise((r) => setTimeout(r, 500));
|
|
366
|
-
}
|
|
367
|
-
const { ResultSet } = await client.send(
|
|
368
|
-
new GetQueryResultsCommand({ QueryExecutionId })
|
|
369
|
-
);
|
|
370
|
-
const resultRows = ResultSet?.Rows ?? [];
|
|
371
|
-
if (resultRows.length === 0) return { rows: [] };
|
|
372
|
-
const headers = resultRows[0].Data?.map((d) => d.VarCharValue ?? "") ?? [];
|
|
373
|
-
const rows = resultRows.slice(1).map((row) => {
|
|
374
|
-
const obj = {};
|
|
375
|
-
row.Data?.forEach((d, i) => {
|
|
376
|
-
obj[headers[i]] = d.VarCharValue ?? null;
|
|
377
|
-
});
|
|
378
|
-
return obj;
|
|
379
|
-
});
|
|
380
|
-
return { rows };
|
|
381
|
-
}
|
|
382
|
-
};
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
// src/connector-client/redshift.ts
|
|
386
|
-
function createRedshiftClient(entry, connectionId) {
|
|
387
|
-
const region = resolveEnvVar(entry, "aws-region", connectionId);
|
|
388
|
-
const accessKeyId = resolveEnvVar(entry, "aws-access-key-id", connectionId);
|
|
389
|
-
const secretAccessKey = resolveEnvVar(entry, "aws-secret-access-key", connectionId);
|
|
390
|
-
const database = resolveEnvVar(entry, "database", connectionId);
|
|
391
|
-
const clusterIdentifier = resolveEnvVarOptional(entry, "cluster-identifier");
|
|
392
|
-
const workgroupName = resolveEnvVarOptional(entry, "workgroup-name");
|
|
393
|
-
const secretArn = resolveEnvVarOptional(entry, "secret-arn");
|
|
394
|
-
const dbUser = resolveEnvVarOptional(entry, "db-user");
|
|
395
|
-
return {
|
|
396
|
-
async query(sql) {
|
|
397
|
-
const {
|
|
398
|
-
RedshiftDataClient,
|
|
399
|
-
ExecuteStatementCommand,
|
|
400
|
-
DescribeStatementCommand,
|
|
401
|
-
GetStatementResultCommand
|
|
402
|
-
} = await import("@aws-sdk/client-redshift-data");
|
|
403
|
-
const client = new RedshiftDataClient({
|
|
404
|
-
region,
|
|
405
|
-
credentials: { accessKeyId, secretAccessKey }
|
|
406
|
-
});
|
|
407
|
-
const executeParams = {
|
|
408
|
-
Sql: sql,
|
|
409
|
-
Database: database
|
|
410
|
-
};
|
|
411
|
-
if (clusterIdentifier) executeParams.ClusterIdentifier = clusterIdentifier;
|
|
412
|
-
if (workgroupName) executeParams.WorkgroupName = workgroupName;
|
|
413
|
-
if (secretArn) executeParams.SecretArn = secretArn;
|
|
414
|
-
if (dbUser) executeParams.DbUser = dbUser;
|
|
415
|
-
const { Id } = await client.send(
|
|
416
|
-
new ExecuteStatementCommand(executeParams)
|
|
417
|
-
);
|
|
418
|
-
if (!Id) throw new Error("Redshift: failed to start statement execution");
|
|
419
|
-
while (true) {
|
|
420
|
-
const desc = await client.send(new DescribeStatementCommand({ Id }));
|
|
421
|
-
const status = desc.Status;
|
|
422
|
-
if (status === "FINISHED") break;
|
|
423
|
-
if (status === "FAILED") {
|
|
424
|
-
throw new Error(`Redshift query failed: ${desc.Error ?? "unknown"}`);
|
|
425
|
-
}
|
|
426
|
-
if (status === "ABORTED") throw new Error("Redshift query was aborted");
|
|
427
|
-
await new Promise((r) => setTimeout(r, 500));
|
|
428
|
-
}
|
|
429
|
-
const result = await client.send(new GetStatementResultCommand({ Id }));
|
|
430
|
-
const columns = result.ColumnMetadata?.map((c) => c.name ?? "") ?? [];
|
|
431
|
-
const rows = (result.Records ?? []).map((record) => {
|
|
432
|
-
const obj = {};
|
|
433
|
-
record.forEach((field, i) => {
|
|
434
|
-
const col = columns[i];
|
|
435
|
-
const value = field.stringValue ?? field.longValue ?? field.doubleValue ?? field.booleanValue ?? (field.isNull ? null : field.blobValue ?? null);
|
|
436
|
-
obj[col] = value;
|
|
437
|
-
});
|
|
438
|
-
return obj;
|
|
439
|
-
});
|
|
440
|
-
return { rows };
|
|
441
|
-
}
|
|
442
|
-
};
|
|
443
|
-
}
|
|
444
|
-
|
|
445
|
-
// src/connector-client/databricks.ts
|
|
446
|
-
function createDatabricksClient(entry, connectionId) {
|
|
447
|
-
const host = resolveEnvVar(entry, "host", connectionId);
|
|
448
|
-
const httpPath = resolveEnvVar(entry, "http-path", connectionId);
|
|
449
|
-
const token = resolveEnvVar(entry, "token", connectionId);
|
|
450
|
-
return {
|
|
451
|
-
async query(sql) {
|
|
452
|
-
const { DBSQLClient } = await import("@databricks/sql");
|
|
453
|
-
const client = new DBSQLClient();
|
|
454
|
-
await client.connect({ host, path: httpPath, token });
|
|
455
|
-
try {
|
|
456
|
-
const session = await client.openSession();
|
|
457
|
-
try {
|
|
458
|
-
const operation = await session.executeStatement(sql);
|
|
459
|
-
const result = await operation.fetchAll();
|
|
460
|
-
await operation.close();
|
|
461
|
-
return { rows: result };
|
|
462
|
-
} finally {
|
|
463
|
-
await session.close();
|
|
464
|
-
}
|
|
465
|
-
} finally {
|
|
466
|
-
await client.close();
|
|
467
|
-
}
|
|
468
|
-
}
|
|
469
|
-
};
|
|
470
|
-
}
|
|
471
|
-
|
|
472
36
|
// src/connector-client/registry.ts
|
|
473
37
|
function createConnectorRegistry() {
|
|
474
|
-
const clientCache = /* @__PURE__ */ new Map();
|
|
475
38
|
function getConnectionsFilePath() {
|
|
476
39
|
return process.env.CONNECTIONS_PATH ?? path.join(process.cwd(), ".squadbase/connections.json");
|
|
477
40
|
}
|
|
@@ -484,59 +47,28 @@ function createConnectorRegistry() {
|
|
|
484
47
|
return {};
|
|
485
48
|
}
|
|
486
49
|
}
|
|
487
|
-
async function
|
|
50
|
+
async function getQuery2(connectionId) {
|
|
488
51
|
const connections = await loadConnections2();
|
|
489
52
|
const entry = connections[connectionId];
|
|
490
53
|
if (!entry) {
|
|
491
|
-
throw new Error(
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
const cached = clientCache.get(connectionId);
|
|
495
|
-
if (cached) return { client: cached, connectorSlug };
|
|
496
|
-
if (connectorSlug === "snowflake") {
|
|
497
|
-
if (entry.connector.authType === "pat") {
|
|
498
|
-
return { client: createSnowflakePatClient(entry, connectionId), connectorSlug };
|
|
499
|
-
}
|
|
500
|
-
return { client: createSnowflakeClient(entry, connectionId), connectorSlug };
|
|
501
|
-
}
|
|
502
|
-
if (connectorSlug === "bigquery") {
|
|
503
|
-
if (entry.connector.authType === "oauth") {
|
|
504
|
-
return { client: createBigQueryOAuthClient(entry, connectionId), connectorSlug };
|
|
505
|
-
}
|
|
506
|
-
return { client: createBigQueryClient(entry, connectionId), connectorSlug };
|
|
507
|
-
}
|
|
508
|
-
if (connectorSlug === "athena") {
|
|
509
|
-
return { client: createAthenaClient(entry, connectionId), connectorSlug };
|
|
510
|
-
}
|
|
511
|
-
if (connectorSlug === "redshift") {
|
|
512
|
-
return { client: createRedshiftClient(entry, connectionId), connectorSlug };
|
|
513
|
-
}
|
|
514
|
-
if (connectorSlug === "databricks") {
|
|
515
|
-
return { client: createDatabricksClient(entry, connectionId), connectorSlug };
|
|
54
|
+
throw new Error(
|
|
55
|
+
`connection '${connectionId}' not found in .squadbase/connections.json`
|
|
56
|
+
);
|
|
516
57
|
}
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
58
|
+
const { slug, authType } = entry.connector;
|
|
59
|
+
const plugin = connectors.findByKey(slug, authType);
|
|
60
|
+
if (!plugin) {
|
|
61
|
+
throw new Error(
|
|
62
|
+
`connector "${slug}" (authType: ${authType ?? "none"}) is not registered in @squadbase/connectors`
|
|
63
|
+
);
|
|
521
64
|
}
|
|
522
|
-
if (
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
}
|
|
527
|
-
const connectionUrl = process.env[urlEnvName];
|
|
528
|
-
if (!connectionUrl) {
|
|
529
|
-
throw new Error(
|
|
530
|
-
`environment variable '${urlEnvName}' (mapped from connection '${connectionId}') is not set`
|
|
531
|
-
);
|
|
532
|
-
}
|
|
533
|
-
const client = createPostgreSQLClient(connectionUrl);
|
|
534
|
-
clientCache.set(connectionId, client);
|
|
535
|
-
return { client, connectorSlug };
|
|
65
|
+
if (!plugin.query) {
|
|
66
|
+
throw new Error(
|
|
67
|
+
`connector "${plugin.connectorKey}" does not support SQL queries. Non-SQL connectors (airtable, google-analytics, kintone, wix-store, dbt) should be used via TypeScript handlers.`
|
|
68
|
+
);
|
|
536
69
|
}
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
);
|
|
70
|
+
const params = resolveParams(entry, connectionId, plugin);
|
|
71
|
+
return (sql, namedParams) => plugin.query(params, sql, namedParams);
|
|
540
72
|
}
|
|
541
73
|
function reloadEnvFile2(envPath) {
|
|
542
74
|
try {
|
|
@@ -559,14 +91,27 @@ function createConnectorRegistry() {
|
|
|
559
91
|
const envPath = path.join(process.cwd(), ".env");
|
|
560
92
|
try {
|
|
561
93
|
fsWatch(filePath, { persistent: false }, () => {
|
|
562
|
-
console.log(
|
|
563
|
-
|
|
94
|
+
console.log(
|
|
95
|
+
"[connector-client] connections.json changed"
|
|
96
|
+
);
|
|
564
97
|
setImmediate(() => reloadEnvFile2(envPath));
|
|
565
98
|
});
|
|
566
99
|
} catch {
|
|
567
100
|
}
|
|
568
101
|
}
|
|
569
|
-
return {
|
|
102
|
+
return { getQuery: getQuery2, loadConnections: loadConnections2, reloadEnvFile: reloadEnvFile2, watchConnectionsFile: watchConnectionsFile2 };
|
|
103
|
+
}
|
|
104
|
+
function resolveParams(entry, connectionId, plugin) {
|
|
105
|
+
const params = {};
|
|
106
|
+
for (const param of Object.values(plugin.parameters)) {
|
|
107
|
+
if (param.required) {
|
|
108
|
+
params[param.slug] = resolveEnvVar(entry, param.slug, connectionId);
|
|
109
|
+
} else {
|
|
110
|
+
const val = resolveEnvVarOptional(entry, param.slug);
|
|
111
|
+
if (val !== void 0) params[param.slug] = val;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return params;
|
|
570
115
|
}
|
|
571
116
|
|
|
572
117
|
// src/connector-client/airtable.ts
|
|
@@ -832,7 +377,7 @@ function createDbtClient(entry, slug) {
|
|
|
832
377
|
}
|
|
833
378
|
|
|
834
379
|
// src/connector-client/index.ts
|
|
835
|
-
var {
|
|
380
|
+
var { getQuery, loadConnections, reloadEnvFile, watchConnectionsFile } = createConnectorRegistry();
|
|
836
381
|
|
|
837
382
|
// src/types/data-source.ts
|
|
838
383
|
import { z } from "zod";
|
|
@@ -927,24 +472,20 @@ async function loadTypeScriptHandler(absolutePath) {
|
|
|
927
472
|
}
|
|
928
473
|
return handler;
|
|
929
474
|
}
|
|
930
|
-
function
|
|
475
|
+
function applyDefaults(parameterMeta, runtimeParams) {
|
|
931
476
|
const defaults = new Map(
|
|
932
477
|
parameterMeta.map((p) => [p.name, p.default ?? null])
|
|
933
478
|
);
|
|
934
|
-
const
|
|
935
|
-
const
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
values.push(value);
|
|
942
|
-
placeholderToIndex.set(name, values.length);
|
|
943
|
-
}
|
|
944
|
-
return `$${placeholderToIndex.get(name)}`;
|
|
479
|
+
const result = {};
|
|
480
|
+
for (const [key, value] of Object.entries(runtimeParams)) {
|
|
481
|
+
result[key] = value;
|
|
482
|
+
}
|
|
483
|
+
for (const [key, defaultVal] of defaults) {
|
|
484
|
+
if (!(key in result)) {
|
|
485
|
+
result[key] = defaultVal;
|
|
945
486
|
}
|
|
946
|
-
|
|
947
|
-
return
|
|
487
|
+
}
|
|
488
|
+
return result;
|
|
948
489
|
}
|
|
949
490
|
var defaultDataSourceDir = path2.join(process.cwd(), "data-source");
|
|
950
491
|
async function initialize() {
|
|
@@ -992,46 +533,12 @@ async function initialize() {
|
|
|
992
533
|
cacheConfig: sqlDef.cache,
|
|
993
534
|
_query: sqlDef.query,
|
|
994
535
|
handler: async (runtimeParams) => {
|
|
995
|
-
const
|
|
996
|
-
const
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
(sqlDef.parameters ?? []).map((p) => [p.name, p.default ?? null])
|
|
1002
|
-
);
|
|
1003
|
-
queryText = sqlDef.query.replace(
|
|
1004
|
-
/\{\{(\w+)\}\}/g,
|
|
1005
|
-
(_match, name) => {
|
|
1006
|
-
const value = Object.prototype.hasOwnProperty.call(
|
|
1007
|
-
runtimeParams,
|
|
1008
|
-
name
|
|
1009
|
-
) ? runtimeParams[name] : defaults.get(name) ?? "";
|
|
1010
|
-
if (typeof value === "string")
|
|
1011
|
-
return `'${value.replace(/'/g, "''")}'`;
|
|
1012
|
-
if (value === null || value === void 0) return "NULL";
|
|
1013
|
-
return String(value);
|
|
1014
|
-
}
|
|
1015
|
-
);
|
|
1016
|
-
queryValues = [];
|
|
1017
|
-
} else if (connectorSlug === "mysql") {
|
|
1018
|
-
const built = buildQuery(
|
|
1019
|
-
sqlDef.query,
|
|
1020
|
-
sqlDef.parameters ?? [],
|
|
1021
|
-
runtimeParams
|
|
1022
|
-
);
|
|
1023
|
-
queryText = built.text.replace(/\$(\d+)/g, "?");
|
|
1024
|
-
queryValues = built.values;
|
|
1025
|
-
} else {
|
|
1026
|
-
const built = buildQuery(
|
|
1027
|
-
sqlDef.query,
|
|
1028
|
-
sqlDef.parameters ?? [],
|
|
1029
|
-
runtimeParams
|
|
1030
|
-
);
|
|
1031
|
-
queryText = built.text;
|
|
1032
|
-
queryValues = built.values;
|
|
1033
|
-
}
|
|
1034
|
-
const result = await client.query(queryText, queryValues);
|
|
536
|
+
const query = await getQuery(sqlDef.connectionId);
|
|
537
|
+
const namedParams = applyDefaults(
|
|
538
|
+
sqlDef.parameters ?? [],
|
|
539
|
+
runtimeParams
|
|
540
|
+
);
|
|
541
|
+
const result = await query(sqlDef.query, namedParams);
|
|
1035
542
|
return result.rows;
|
|
1036
543
|
}
|
|
1037
544
|
};
|
|
@@ -1428,6 +935,75 @@ app4.get("/runtime-data", (c) => {
|
|
|
1428
935
|
});
|
|
1429
936
|
var pages_default = app4;
|
|
1430
937
|
|
|
938
|
+
// src/connection.ts
|
|
939
|
+
import { getContext } from "hono/context-storage";
|
|
940
|
+
import { getCookie } from "hono/cookie";
|
|
941
|
+
var APP_SESSION_COOKIE_NAME = "__Host-squadbase-session";
|
|
942
|
+
var PREVIEW_SESSION_COOKIE_NAME = "squadbase-preview-session";
|
|
943
|
+
var APP_BASE_DOMAIN = "squadbase.app";
|
|
944
|
+
var PREVIEW_BASE_DOMAIN = "preview.app.squadbase.dev";
|
|
945
|
+
var SANDBOX_ID_ENV_NAME = "INTERNAL_SQUADBASE_SANDBOX_ID";
|
|
946
|
+
var MACHINE_CREDENTIAL_ENV_NAME = "INTERNAL_SQUADBASE_OAUTH_MACHINE_CREDENTIAL";
|
|
947
|
+
function resolveProxyUrl(connectionId) {
|
|
948
|
+
const connectionPath = `/_sqcore/connections/${connectionId}/request`;
|
|
949
|
+
const sandboxId = process.env[SANDBOX_ID_ENV_NAME];
|
|
950
|
+
if (sandboxId) {
|
|
951
|
+
const baseDomain2 = process.env["SQUADBASE_PREVIEW_BASE_DOMAIN"] ?? PREVIEW_BASE_DOMAIN;
|
|
952
|
+
return `https://${sandboxId}.${baseDomain2}${connectionPath}`;
|
|
953
|
+
}
|
|
954
|
+
const projectId = process.env["SQUADBASE_PROJECT_ID"];
|
|
955
|
+
if (!projectId) {
|
|
956
|
+
throw new Error(
|
|
957
|
+
"Project ID is required. Please set SQUADBASE_PROJECT_ID environment variable."
|
|
958
|
+
);
|
|
959
|
+
}
|
|
960
|
+
const baseDomain = process.env["SQUADBASE_APP_BASE_DOMAIN"] ?? APP_BASE_DOMAIN;
|
|
961
|
+
return `https://${projectId}.${baseDomain}${connectionPath}`;
|
|
962
|
+
}
|
|
963
|
+
function resolveAuthHeaders() {
|
|
964
|
+
const machineCredential = process.env[MACHINE_CREDENTIAL_ENV_NAME];
|
|
965
|
+
if (machineCredential) {
|
|
966
|
+
return { Authorization: `Bearer ${machineCredential}` };
|
|
967
|
+
}
|
|
968
|
+
const c = getContext();
|
|
969
|
+
const cookies = getCookie(c);
|
|
970
|
+
const previewSession = cookies[PREVIEW_SESSION_COOKIE_NAME];
|
|
971
|
+
if (previewSession) {
|
|
972
|
+
return {
|
|
973
|
+
Cookie: `${PREVIEW_SESSION_COOKIE_NAME}=${previewSession}`
|
|
974
|
+
};
|
|
975
|
+
}
|
|
976
|
+
const appSession = cookies[APP_SESSION_COOKIE_NAME];
|
|
977
|
+
if (appSession) {
|
|
978
|
+
return { Authorization: `Bearer ${appSession}` };
|
|
979
|
+
}
|
|
980
|
+
throw new Error(
|
|
981
|
+
"No authentication method available for connection proxy. Expected one of: INTERNAL_SQUADBASE_OAUTH_MACHINE_CREDENTIAL env var, preview session cookie, or app session cookie."
|
|
982
|
+
);
|
|
983
|
+
}
|
|
984
|
+
function connection(connectionId) {
|
|
985
|
+
return {
|
|
986
|
+
async fetch(url, options) {
|
|
987
|
+
const proxyUrl = resolveProxyUrl(connectionId);
|
|
988
|
+
const authHeaders = resolveAuthHeaders();
|
|
989
|
+
return await fetch(proxyUrl, {
|
|
990
|
+
method: "POST",
|
|
991
|
+
headers: {
|
|
992
|
+
"Content-Type": "application/json",
|
|
993
|
+
...authHeaders
|
|
994
|
+
},
|
|
995
|
+
body: JSON.stringify({
|
|
996
|
+
url,
|
|
997
|
+
method: options?.method,
|
|
998
|
+
headers: options?.headers,
|
|
999
|
+
body: options?.body,
|
|
1000
|
+
timeoutMs: options?.timeoutMs
|
|
1001
|
+
})
|
|
1002
|
+
});
|
|
1003
|
+
}
|
|
1004
|
+
};
|
|
1005
|
+
}
|
|
1006
|
+
|
|
1431
1007
|
// src/index.ts
|
|
1432
1008
|
var apiApp = new Hono5();
|
|
1433
1009
|
apiApp.use("/*", contextStorage());
|
|
@@ -1452,6 +1028,6 @@ export {
|
|
|
1452
1028
|
createKintoneClient,
|
|
1453
1029
|
createWixStoreClient,
|
|
1454
1030
|
src_default as default,
|
|
1455
|
-
|
|
1031
|
+
getQuery,
|
|
1456
1032
|
loadConnections
|
|
1457
1033
|
};
|