@squadbase/vite-server 0.0.1-build-11 → 0.0.1-build-13
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 +197 -4
- package/dist/index.js +193 -69
- package/dist/main.js +193 -4
- package/dist/vite-plugin.js +197 -4
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -144,6 +144,152 @@ function createBigQueryClient(entry, connectionId) {
|
|
|
144
144
|
};
|
|
145
145
|
}
|
|
146
146
|
|
|
147
|
+
// src/connection.ts
|
|
148
|
+
import { getContext } from "hono/context-storage";
|
|
149
|
+
import { getCookie } from "hono/cookie";
|
|
150
|
+
var APP_SESSION_COOKIE_NAME = "__Host-squadbase-session";
|
|
151
|
+
var PREVIEW_SESSION_COOKIE_NAME = "squadbase-preview-session";
|
|
152
|
+
var APP_BASE_DOMAIN = "squadbase.app";
|
|
153
|
+
var PREVIEW_BASE_DOMAIN = "preview.app.squadbase.dev";
|
|
154
|
+
var SANDBOX_ID_ENV_NAME = "INTERNAL_SQUADBASE_SANDBOX_ID";
|
|
155
|
+
var MACHINE_CREDENTIAL_ENV_NAME = "INTERNAL_SQUADBASE_OAUTH_MACHINE_CREDENTIAL";
|
|
156
|
+
function resolveProxyUrl(connectionId) {
|
|
157
|
+
const connectionPath = `/_sqcore/connections/${connectionId}/request`;
|
|
158
|
+
const sandboxId = process.env[SANDBOX_ID_ENV_NAME];
|
|
159
|
+
if (sandboxId) {
|
|
160
|
+
const baseDomain2 = process.env["SQUADBASE_PREVIEW_BASE_DOMAIN"] ?? PREVIEW_BASE_DOMAIN;
|
|
161
|
+
return `https://${sandboxId}.${baseDomain2}${connectionPath}`;
|
|
162
|
+
}
|
|
163
|
+
const projectId = process.env["SQUADBASE_PROJECT_ID"];
|
|
164
|
+
if (!projectId) {
|
|
165
|
+
throw new Error(
|
|
166
|
+
"Project ID is required. Please set SQUADBASE_PROJECT_ID environment variable."
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
const baseDomain = process.env["SQUADBASE_APP_BASE_DOMAIN"] ?? APP_BASE_DOMAIN;
|
|
170
|
+
return `https://${projectId}.${baseDomain}${connectionPath}`;
|
|
171
|
+
}
|
|
172
|
+
function resolveAuthHeaders() {
|
|
173
|
+
const machineCredential = process.env[MACHINE_CREDENTIAL_ENV_NAME];
|
|
174
|
+
if (machineCredential) {
|
|
175
|
+
return { Authorization: `Bearer ${machineCredential}` };
|
|
176
|
+
}
|
|
177
|
+
const c = getContext();
|
|
178
|
+
const cookies = getCookie(c);
|
|
179
|
+
const previewSession = cookies[PREVIEW_SESSION_COOKIE_NAME];
|
|
180
|
+
if (previewSession) {
|
|
181
|
+
return {
|
|
182
|
+
Cookie: `${PREVIEW_SESSION_COOKIE_NAME}=${previewSession}`
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
const appSession = cookies[APP_SESSION_COOKIE_NAME];
|
|
186
|
+
if (appSession) {
|
|
187
|
+
return { Authorization: `Bearer ${appSession}` };
|
|
188
|
+
}
|
|
189
|
+
throw new Error(
|
|
190
|
+
"No authentication method available for connection proxy. Expected one of: INTERNAL_SQUADBASE_OAUTH_MACHINE_CREDENTIAL env var, preview session cookie, or app session cookie."
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
function connection(connectionId) {
|
|
194
|
+
return {
|
|
195
|
+
async fetch(url, options) {
|
|
196
|
+
const proxyUrl = resolveProxyUrl(connectionId);
|
|
197
|
+
const authHeaders = resolveAuthHeaders();
|
|
198
|
+
return await fetch(proxyUrl, {
|
|
199
|
+
method: "POST",
|
|
200
|
+
headers: {
|
|
201
|
+
"Content-Type": "application/json",
|
|
202
|
+
...authHeaders
|
|
203
|
+
},
|
|
204
|
+
body: JSON.stringify({
|
|
205
|
+
url,
|
|
206
|
+
method: options?.method,
|
|
207
|
+
headers: options?.headers,
|
|
208
|
+
body: options?.body,
|
|
209
|
+
timeoutMs: options?.timeoutMs
|
|
210
|
+
})
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// src/connector-client/bigquery-oauth.ts
|
|
217
|
+
var MAX_RESULTS = 1e4;
|
|
218
|
+
var POLL_INTERVAL_MS = 1e3;
|
|
219
|
+
var POLL_TIMEOUT_MS = 12e4;
|
|
220
|
+
function flattenRows(fields, rows) {
|
|
221
|
+
return rows.map((row) => {
|
|
222
|
+
const obj = {};
|
|
223
|
+
for (let i = 0; i < fields.length; i++) {
|
|
224
|
+
obj[fields[i].name] = row.f[i].v;
|
|
225
|
+
}
|
|
226
|
+
return obj;
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
function createBigQueryOAuthClient(entry, connectionId) {
|
|
230
|
+
const projectId = resolveEnvVar(entry, "project-id", connectionId);
|
|
231
|
+
const baseUrl = `https://bigquery.googleapis.com/bigquery/v2/projects/${projectId}`;
|
|
232
|
+
return {
|
|
233
|
+
async query(sql) {
|
|
234
|
+
const conn = connection(connectionId);
|
|
235
|
+
const res = await conn.fetch(`${baseUrl}/queries`, {
|
|
236
|
+
method: "POST",
|
|
237
|
+
body: {
|
|
238
|
+
query: sql,
|
|
239
|
+
useLegacySql: false,
|
|
240
|
+
maxResults: MAX_RESULTS
|
|
241
|
+
}
|
|
242
|
+
});
|
|
243
|
+
if (!res.ok) {
|
|
244
|
+
const text = await res.text().catch(() => res.statusText);
|
|
245
|
+
throw new Error(`BigQuery query failed: HTTP ${res.status} ${text}`);
|
|
246
|
+
}
|
|
247
|
+
let data = await res.json();
|
|
248
|
+
if (data.errors?.length) {
|
|
249
|
+
throw new Error(
|
|
250
|
+
`BigQuery query error: ${data.errors.map((e) => e.message).join("; ")}`
|
|
251
|
+
);
|
|
252
|
+
}
|
|
253
|
+
if (!data.jobComplete) {
|
|
254
|
+
const jobId = data.jobReference.jobId;
|
|
255
|
+
const location = data.jobReference.location;
|
|
256
|
+
const deadline = Date.now() + POLL_TIMEOUT_MS;
|
|
257
|
+
while (!data.jobComplete) {
|
|
258
|
+
if (Date.now() > deadline) {
|
|
259
|
+
throw new Error(
|
|
260
|
+
`BigQuery query timed out after ${POLL_TIMEOUT_MS / 1e3}s (jobId: ${jobId})`
|
|
261
|
+
);
|
|
262
|
+
}
|
|
263
|
+
await new Promise((r) => setTimeout(r, POLL_INTERVAL_MS));
|
|
264
|
+
const params = new URLSearchParams({
|
|
265
|
+
maxResults: String(MAX_RESULTS)
|
|
266
|
+
});
|
|
267
|
+
if (location) params.set("location", location);
|
|
268
|
+
const pollRes = await conn.fetch(
|
|
269
|
+
`${baseUrl}/queries/${jobId}?${params}`,
|
|
270
|
+
{ method: "GET" }
|
|
271
|
+
);
|
|
272
|
+
if (!pollRes.ok) {
|
|
273
|
+
const text = await pollRes.text().catch(() => pollRes.statusText);
|
|
274
|
+
throw new Error(
|
|
275
|
+
`BigQuery poll failed: HTTP ${pollRes.status} ${text}`
|
|
276
|
+
);
|
|
277
|
+
}
|
|
278
|
+
data = await pollRes.json();
|
|
279
|
+
if (data.errors?.length) {
|
|
280
|
+
throw new Error(
|
|
281
|
+
`BigQuery query error: ${data.errors.map((e) => e.message).join("; ")}`
|
|
282
|
+
);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
const fields = data.schema?.fields ?? [];
|
|
287
|
+
const rawRows = data.rows ?? [];
|
|
288
|
+
return { rows: flattenRows(fields, rawRows) };
|
|
289
|
+
}
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
|
|
147
293
|
// src/connector-client/snowflake.ts
|
|
148
294
|
function createSnowflakeClient(entry, connectionId) {
|
|
149
295
|
const accountIdentifier = resolveEnvVar(entry, "account", connectionId);
|
|
@@ -156,7 +302,7 @@ function createSnowflakeClient(entry, connectionId) {
|
|
|
156
302
|
async query(sql) {
|
|
157
303
|
const snowflake = (await import("snowflake-sdk")).default;
|
|
158
304
|
snowflake.configure({ logLevel: "ERROR" });
|
|
159
|
-
const
|
|
305
|
+
const connection2 = snowflake.createConnection({
|
|
160
306
|
account: accountIdentifier,
|
|
161
307
|
username: user,
|
|
162
308
|
role,
|
|
@@ -165,13 +311,13 @@ function createSnowflakeClient(entry, connectionId) {
|
|
|
165
311
|
privateKey
|
|
166
312
|
});
|
|
167
313
|
await new Promise((resolve, reject) => {
|
|
168
|
-
|
|
314
|
+
connection2.connect((err) => {
|
|
169
315
|
if (err) reject(new Error(`Snowflake connect failed: ${err.message}`));
|
|
170
316
|
else resolve();
|
|
171
317
|
});
|
|
172
318
|
});
|
|
173
319
|
const rows = await new Promise((resolve, reject) => {
|
|
174
|
-
|
|
320
|
+
connection2.execute({
|
|
175
321
|
sqlText: sql,
|
|
176
322
|
complete: (err, _stmt, rows2) => {
|
|
177
323
|
if (err) reject(new Error(`Snowflake query failed: ${err.message}`));
|
|
@@ -179,7 +325,48 @@ function createSnowflakeClient(entry, connectionId) {
|
|
|
179
325
|
}
|
|
180
326
|
});
|
|
181
327
|
});
|
|
182
|
-
|
|
328
|
+
connection2.destroy((err) => {
|
|
329
|
+
if (err) console.warn(`[connector-client] Snowflake destroy error: ${err.message}`);
|
|
330
|
+
});
|
|
331
|
+
return { rows };
|
|
332
|
+
}
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// src/connector-client/snowflake-pat.ts
|
|
337
|
+
function createSnowflakePatClient(entry, connectionId) {
|
|
338
|
+
const accountIdentifier = resolveEnvVar(entry, "account", connectionId);
|
|
339
|
+
const user = resolveEnvVar(entry, "user", connectionId);
|
|
340
|
+
const role = resolveEnvVar(entry, "role", connectionId);
|
|
341
|
+
const warehouse = resolveEnvVar(entry, "warehouse", connectionId);
|
|
342
|
+
const password = resolveEnvVar(entry, "pat", connectionId);
|
|
343
|
+
return {
|
|
344
|
+
async query(sql) {
|
|
345
|
+
const snowflake = (await import("snowflake-sdk")).default;
|
|
346
|
+
snowflake.configure({ logLevel: "ERROR" });
|
|
347
|
+
const connection2 = snowflake.createConnection({
|
|
348
|
+
account: accountIdentifier,
|
|
349
|
+
username: user,
|
|
350
|
+
role,
|
|
351
|
+
warehouse,
|
|
352
|
+
password
|
|
353
|
+
});
|
|
354
|
+
await new Promise((resolve, reject) => {
|
|
355
|
+
connection2.connect((err) => {
|
|
356
|
+
if (err) reject(new Error(`Snowflake connect failed: ${err.message}`));
|
|
357
|
+
else resolve();
|
|
358
|
+
});
|
|
359
|
+
});
|
|
360
|
+
const rows = await new Promise((resolve, reject) => {
|
|
361
|
+
connection2.execute({
|
|
362
|
+
sqlText: sql,
|
|
363
|
+
complete: (err, _stmt, rows2) => {
|
|
364
|
+
if (err) reject(new Error(`Snowflake query failed: ${err.message}`));
|
|
365
|
+
else resolve(rows2 ?? []);
|
|
366
|
+
}
|
|
367
|
+
});
|
|
368
|
+
});
|
|
369
|
+
connection2.destroy((err) => {
|
|
183
370
|
if (err) console.warn(`[connector-client] Snowflake destroy error: ${err.message}`);
|
|
184
371
|
});
|
|
185
372
|
return { rows };
|
|
@@ -382,9 +569,15 @@ function createConnectorRegistry() {
|
|
|
382
569
|
const cached = clientCache.get(connectionId);
|
|
383
570
|
if (cached) return { client: cached, connectorSlug };
|
|
384
571
|
if (connectorSlug === "snowflake") {
|
|
572
|
+
if (entry.connector.authType === "pat") {
|
|
573
|
+
return { client: createSnowflakePatClient(entry, connectionId), connectorSlug };
|
|
574
|
+
}
|
|
385
575
|
return { client: createSnowflakeClient(entry, connectionId), connectorSlug };
|
|
386
576
|
}
|
|
387
577
|
if (connectorSlug === "bigquery") {
|
|
578
|
+
if (entry.connector.authType === "oauth") {
|
|
579
|
+
return { client: createBigQueryOAuthClient(entry, connectionId), connectorSlug };
|
|
580
|
+
}
|
|
388
581
|
return { client: createBigQueryClient(entry, connectionId), connectorSlug };
|
|
389
582
|
}
|
|
390
583
|
if (connectorSlug === "athena") {
|
package/dist/index.js
CHANGED
|
@@ -69,6 +69,152 @@ function createBigQueryClient(entry, connectionId) {
|
|
|
69
69
|
};
|
|
70
70
|
}
|
|
71
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
|
+
|
|
72
218
|
// src/connector-client/snowflake.ts
|
|
73
219
|
function createSnowflakeClient(entry, connectionId) {
|
|
74
220
|
const accountIdentifier = resolveEnvVar(entry, "account", connectionId);
|
|
@@ -112,6 +258,47 @@ function createSnowflakeClient(entry, connectionId) {
|
|
|
112
258
|
};
|
|
113
259
|
}
|
|
114
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
|
+
|
|
115
302
|
// src/connector-client/mysql.ts
|
|
116
303
|
function createMySQLClient(entry, connectionId) {
|
|
117
304
|
const connectionUrl = resolveEnvVar(entry, "connection-url", connectionId);
|
|
@@ -307,9 +494,15 @@ function createConnectorRegistry() {
|
|
|
307
494
|
const cached = clientCache.get(connectionId);
|
|
308
495
|
if (cached) return { client: cached, connectorSlug };
|
|
309
496
|
if (connectorSlug === "snowflake") {
|
|
497
|
+
if (entry.connector.authType === "pat") {
|
|
498
|
+
return { client: createSnowflakePatClient(entry, connectionId), connectorSlug };
|
|
499
|
+
}
|
|
310
500
|
return { client: createSnowflakeClient(entry, connectionId), connectorSlug };
|
|
311
501
|
}
|
|
312
502
|
if (connectorSlug === "bigquery") {
|
|
503
|
+
if (entry.connector.authType === "oauth") {
|
|
504
|
+
return { client: createBigQueryOAuthClient(entry, connectionId), connectorSlug };
|
|
505
|
+
}
|
|
313
506
|
return { client: createBigQueryClient(entry, connectionId), connectorSlug };
|
|
314
507
|
}
|
|
315
508
|
if (connectorSlug === "athena") {
|
|
@@ -1235,75 +1428,6 @@ app4.get("/runtime-data", (c) => {
|
|
|
1235
1428
|
});
|
|
1236
1429
|
var pages_default = app4;
|
|
1237
1430
|
|
|
1238
|
-
// src/connection.ts
|
|
1239
|
-
import { getContext } from "hono/context-storage";
|
|
1240
|
-
import { getCookie } from "hono/cookie";
|
|
1241
|
-
var APP_SESSION_COOKIE_NAME = "__Host-squadbase-session";
|
|
1242
|
-
var PREVIEW_SESSION_COOKIE_NAME = "squadbase-preview-session";
|
|
1243
|
-
var APP_BASE_DOMAIN = "squadbase.app";
|
|
1244
|
-
var PREVIEW_BASE_DOMAIN = "preview.app.squadbase.dev";
|
|
1245
|
-
var SANDBOX_ID_ENV_NAME = "INTERNAL_SQUADBASE_SANDBOX_ID";
|
|
1246
|
-
var MACHINE_CREDENTIAL_ENV_NAME = "INTERNAL_SQUADBASE_OAUTH_MACHINE_CREDENTIAL";
|
|
1247
|
-
function resolveProxyUrl(connectionId) {
|
|
1248
|
-
const connectionPath = `/_sqcore/connections/${connectionId}/request`;
|
|
1249
|
-
const sandboxId = process.env[SANDBOX_ID_ENV_NAME];
|
|
1250
|
-
if (sandboxId) {
|
|
1251
|
-
const baseDomain2 = process.env["SQUADBASE_PREVIEW_BASE_DOMAIN"] ?? PREVIEW_BASE_DOMAIN;
|
|
1252
|
-
return `https://${sandboxId}.${baseDomain2}${connectionPath}`;
|
|
1253
|
-
}
|
|
1254
|
-
const projectId = process.env["SQUADBASE_PROJECT_ID"];
|
|
1255
|
-
if (!projectId) {
|
|
1256
|
-
throw new Error(
|
|
1257
|
-
"Project ID is required. Please set SQUADBASE_PROJECT_ID environment variable."
|
|
1258
|
-
);
|
|
1259
|
-
}
|
|
1260
|
-
const baseDomain = process.env["SQUADBASE_APP_BASE_DOMAIN"] ?? APP_BASE_DOMAIN;
|
|
1261
|
-
return `https://${projectId}.${baseDomain}${connectionPath}`;
|
|
1262
|
-
}
|
|
1263
|
-
function resolveAuthHeaders() {
|
|
1264
|
-
const machineCredential = process.env[MACHINE_CREDENTIAL_ENV_NAME];
|
|
1265
|
-
if (machineCredential) {
|
|
1266
|
-
return { Authorization: `Bearer ${machineCredential}` };
|
|
1267
|
-
}
|
|
1268
|
-
const c = getContext();
|
|
1269
|
-
const cookies = getCookie(c);
|
|
1270
|
-
const previewSession = cookies[PREVIEW_SESSION_COOKIE_NAME];
|
|
1271
|
-
if (previewSession) {
|
|
1272
|
-
return {
|
|
1273
|
-
Cookie: `${PREVIEW_SESSION_COOKIE_NAME}=${previewSession}`
|
|
1274
|
-
};
|
|
1275
|
-
}
|
|
1276
|
-
const appSession = cookies[APP_SESSION_COOKIE_NAME];
|
|
1277
|
-
if (appSession) {
|
|
1278
|
-
return { Authorization: `Bearer ${appSession}` };
|
|
1279
|
-
}
|
|
1280
|
-
throw new Error(
|
|
1281
|
-
"No authentication method available for connection proxy. Expected one of: INTERNAL_SQUADBASE_OAUTH_MACHINE_CREDENTIAL env var, preview session cookie, or app session cookie."
|
|
1282
|
-
);
|
|
1283
|
-
}
|
|
1284
|
-
function connection(connectionId) {
|
|
1285
|
-
return {
|
|
1286
|
-
async fetch(url, options) {
|
|
1287
|
-
const proxyUrl = resolveProxyUrl(connectionId);
|
|
1288
|
-
const authHeaders = resolveAuthHeaders();
|
|
1289
|
-
return await fetch(proxyUrl, {
|
|
1290
|
-
method: "POST",
|
|
1291
|
-
headers: {
|
|
1292
|
-
"Content-Type": "application/json",
|
|
1293
|
-
...authHeaders
|
|
1294
|
-
},
|
|
1295
|
-
body: JSON.stringify({
|
|
1296
|
-
url,
|
|
1297
|
-
method: options?.method,
|
|
1298
|
-
headers: options?.headers,
|
|
1299
|
-
body: options?.body,
|
|
1300
|
-
timeoutMs: options?.timeoutMs
|
|
1301
|
-
})
|
|
1302
|
-
});
|
|
1303
|
-
}
|
|
1304
|
-
};
|
|
1305
|
-
}
|
|
1306
|
-
|
|
1307
1431
|
// src/index.ts
|
|
1308
1432
|
var apiApp = new Hono5();
|
|
1309
1433
|
apiApp.use("/*", contextStorage());
|
package/dist/main.js
CHANGED
|
@@ -69,6 +69,152 @@ function createBigQueryClient(entry, connectionId) {
|
|
|
69
69
|
};
|
|
70
70
|
}
|
|
71
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
|
+
|
|
72
218
|
// src/connector-client/snowflake.ts
|
|
73
219
|
function createSnowflakeClient(entry, connectionId) {
|
|
74
220
|
const accountIdentifier = resolveEnvVar(entry, "account", connectionId);
|
|
@@ -112,6 +258,47 @@ function createSnowflakeClient(entry, connectionId) {
|
|
|
112
258
|
};
|
|
113
259
|
}
|
|
114
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
|
+
|
|
115
302
|
// src/connector-client/mysql.ts
|
|
116
303
|
function createMySQLClient(entry, connectionId) {
|
|
117
304
|
const connectionUrl = resolveEnvVar(entry, "connection-url", connectionId);
|
|
@@ -307,9 +494,15 @@ function createConnectorRegistry() {
|
|
|
307
494
|
const cached = clientCache.get(connectionId);
|
|
308
495
|
if (cached) return { client: cached, connectorSlug };
|
|
309
496
|
if (connectorSlug === "snowflake") {
|
|
497
|
+
if (entry.connector.authType === "pat") {
|
|
498
|
+
return { client: createSnowflakePatClient(entry, connectionId), connectorSlug };
|
|
499
|
+
}
|
|
310
500
|
return { client: createSnowflakeClient(entry, connectionId), connectorSlug };
|
|
311
501
|
}
|
|
312
502
|
if (connectorSlug === "bigquery") {
|
|
503
|
+
if (entry.connector.authType === "oauth") {
|
|
504
|
+
return { client: createBigQueryOAuthClient(entry, connectionId), connectorSlug };
|
|
505
|
+
}
|
|
313
506
|
return { client: createBigQueryClient(entry, connectionId), connectorSlug };
|
|
314
507
|
}
|
|
315
508
|
if (connectorSlug === "athena") {
|
|
@@ -973,10 +1166,6 @@ app4.get("/runtime-data", (c) => {
|
|
|
973
1166
|
});
|
|
974
1167
|
var pages_default = app4;
|
|
975
1168
|
|
|
976
|
-
// src/connection.ts
|
|
977
|
-
import { getContext } from "hono/context-storage";
|
|
978
|
-
import { getCookie } from "hono/cookie";
|
|
979
|
-
|
|
980
1169
|
// src/index.ts
|
|
981
1170
|
var apiApp = new Hono5();
|
|
982
1171
|
apiApp.use("/*", contextStorage());
|
package/dist/vite-plugin.js
CHANGED
|
@@ -70,6 +70,152 @@ function createBigQueryClient(entry, connectionId) {
|
|
|
70
70
|
};
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
+
// src/connection.ts
|
|
74
|
+
import { getContext } from "hono/context-storage";
|
|
75
|
+
import { getCookie } from "hono/cookie";
|
|
76
|
+
var APP_SESSION_COOKIE_NAME = "__Host-squadbase-session";
|
|
77
|
+
var PREVIEW_SESSION_COOKIE_NAME = "squadbase-preview-session";
|
|
78
|
+
var APP_BASE_DOMAIN = "squadbase.app";
|
|
79
|
+
var PREVIEW_BASE_DOMAIN = "preview.app.squadbase.dev";
|
|
80
|
+
var SANDBOX_ID_ENV_NAME = "INTERNAL_SQUADBASE_SANDBOX_ID";
|
|
81
|
+
var MACHINE_CREDENTIAL_ENV_NAME = "INTERNAL_SQUADBASE_OAUTH_MACHINE_CREDENTIAL";
|
|
82
|
+
function resolveProxyUrl(connectionId) {
|
|
83
|
+
const connectionPath = `/_sqcore/connections/${connectionId}/request`;
|
|
84
|
+
const sandboxId = process.env[SANDBOX_ID_ENV_NAME];
|
|
85
|
+
if (sandboxId) {
|
|
86
|
+
const baseDomain2 = process.env["SQUADBASE_PREVIEW_BASE_DOMAIN"] ?? PREVIEW_BASE_DOMAIN;
|
|
87
|
+
return `https://${sandboxId}.${baseDomain2}${connectionPath}`;
|
|
88
|
+
}
|
|
89
|
+
const projectId = process.env["SQUADBASE_PROJECT_ID"];
|
|
90
|
+
if (!projectId) {
|
|
91
|
+
throw new Error(
|
|
92
|
+
"Project ID is required. Please set SQUADBASE_PROJECT_ID environment variable."
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
const baseDomain = process.env["SQUADBASE_APP_BASE_DOMAIN"] ?? APP_BASE_DOMAIN;
|
|
96
|
+
return `https://${projectId}.${baseDomain}${connectionPath}`;
|
|
97
|
+
}
|
|
98
|
+
function resolveAuthHeaders() {
|
|
99
|
+
const machineCredential = process.env[MACHINE_CREDENTIAL_ENV_NAME];
|
|
100
|
+
if (machineCredential) {
|
|
101
|
+
return { Authorization: `Bearer ${machineCredential}` };
|
|
102
|
+
}
|
|
103
|
+
const c = getContext();
|
|
104
|
+
const cookies = getCookie(c);
|
|
105
|
+
const previewSession = cookies[PREVIEW_SESSION_COOKIE_NAME];
|
|
106
|
+
if (previewSession) {
|
|
107
|
+
return {
|
|
108
|
+
Cookie: `${PREVIEW_SESSION_COOKIE_NAME}=${previewSession}`
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
const appSession = cookies[APP_SESSION_COOKIE_NAME];
|
|
112
|
+
if (appSession) {
|
|
113
|
+
return { Authorization: `Bearer ${appSession}` };
|
|
114
|
+
}
|
|
115
|
+
throw new Error(
|
|
116
|
+
"No authentication method available for connection proxy. Expected one of: INTERNAL_SQUADBASE_OAUTH_MACHINE_CREDENTIAL env var, preview session cookie, or app session cookie."
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
function connection(connectionId) {
|
|
120
|
+
return {
|
|
121
|
+
async fetch(url, options) {
|
|
122
|
+
const proxyUrl = resolveProxyUrl(connectionId);
|
|
123
|
+
const authHeaders = resolveAuthHeaders();
|
|
124
|
+
return await fetch(proxyUrl, {
|
|
125
|
+
method: "POST",
|
|
126
|
+
headers: {
|
|
127
|
+
"Content-Type": "application/json",
|
|
128
|
+
...authHeaders
|
|
129
|
+
},
|
|
130
|
+
body: JSON.stringify({
|
|
131
|
+
url,
|
|
132
|
+
method: options?.method,
|
|
133
|
+
headers: options?.headers,
|
|
134
|
+
body: options?.body,
|
|
135
|
+
timeoutMs: options?.timeoutMs
|
|
136
|
+
})
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// src/connector-client/bigquery-oauth.ts
|
|
143
|
+
var MAX_RESULTS = 1e4;
|
|
144
|
+
var POLL_INTERVAL_MS = 1e3;
|
|
145
|
+
var POLL_TIMEOUT_MS = 12e4;
|
|
146
|
+
function flattenRows(fields, rows) {
|
|
147
|
+
return rows.map((row) => {
|
|
148
|
+
const obj = {};
|
|
149
|
+
for (let i = 0; i < fields.length; i++) {
|
|
150
|
+
obj[fields[i].name] = row.f[i].v;
|
|
151
|
+
}
|
|
152
|
+
return obj;
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
function createBigQueryOAuthClient(entry, connectionId) {
|
|
156
|
+
const projectId = resolveEnvVar(entry, "project-id", connectionId);
|
|
157
|
+
const baseUrl = `https://bigquery.googleapis.com/bigquery/v2/projects/${projectId}`;
|
|
158
|
+
return {
|
|
159
|
+
async query(sql) {
|
|
160
|
+
const conn = connection(connectionId);
|
|
161
|
+
const res = await conn.fetch(`${baseUrl}/queries`, {
|
|
162
|
+
method: "POST",
|
|
163
|
+
body: {
|
|
164
|
+
query: sql,
|
|
165
|
+
useLegacySql: false,
|
|
166
|
+
maxResults: MAX_RESULTS
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
if (!res.ok) {
|
|
170
|
+
const text = await res.text().catch(() => res.statusText);
|
|
171
|
+
throw new Error(`BigQuery query failed: HTTP ${res.status} ${text}`);
|
|
172
|
+
}
|
|
173
|
+
let data = await res.json();
|
|
174
|
+
if (data.errors?.length) {
|
|
175
|
+
throw new Error(
|
|
176
|
+
`BigQuery query error: ${data.errors.map((e) => e.message).join("; ")}`
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
if (!data.jobComplete) {
|
|
180
|
+
const jobId = data.jobReference.jobId;
|
|
181
|
+
const location = data.jobReference.location;
|
|
182
|
+
const deadline = Date.now() + POLL_TIMEOUT_MS;
|
|
183
|
+
while (!data.jobComplete) {
|
|
184
|
+
if (Date.now() > deadline) {
|
|
185
|
+
throw new Error(
|
|
186
|
+
`BigQuery query timed out after ${POLL_TIMEOUT_MS / 1e3}s (jobId: ${jobId})`
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
await new Promise((r) => setTimeout(r, POLL_INTERVAL_MS));
|
|
190
|
+
const params = new URLSearchParams({
|
|
191
|
+
maxResults: String(MAX_RESULTS)
|
|
192
|
+
});
|
|
193
|
+
if (location) params.set("location", location);
|
|
194
|
+
const pollRes = await conn.fetch(
|
|
195
|
+
`${baseUrl}/queries/${jobId}?${params}`,
|
|
196
|
+
{ method: "GET" }
|
|
197
|
+
);
|
|
198
|
+
if (!pollRes.ok) {
|
|
199
|
+
const text = await pollRes.text().catch(() => pollRes.statusText);
|
|
200
|
+
throw new Error(
|
|
201
|
+
`BigQuery poll failed: HTTP ${pollRes.status} ${text}`
|
|
202
|
+
);
|
|
203
|
+
}
|
|
204
|
+
data = await pollRes.json();
|
|
205
|
+
if (data.errors?.length) {
|
|
206
|
+
throw new Error(
|
|
207
|
+
`BigQuery query error: ${data.errors.map((e) => e.message).join("; ")}`
|
|
208
|
+
);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
const fields = data.schema?.fields ?? [];
|
|
213
|
+
const rawRows = data.rows ?? [];
|
|
214
|
+
return { rows: flattenRows(fields, rawRows) };
|
|
215
|
+
}
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
|
|
73
219
|
// src/connector-client/snowflake.ts
|
|
74
220
|
function createSnowflakeClient(entry, connectionId) {
|
|
75
221
|
const accountIdentifier = resolveEnvVar(entry, "account", connectionId);
|
|
@@ -82,7 +228,7 @@ function createSnowflakeClient(entry, connectionId) {
|
|
|
82
228
|
async query(sql) {
|
|
83
229
|
const snowflake = (await import("snowflake-sdk")).default;
|
|
84
230
|
snowflake.configure({ logLevel: "ERROR" });
|
|
85
|
-
const
|
|
231
|
+
const connection2 = snowflake.createConnection({
|
|
86
232
|
account: accountIdentifier,
|
|
87
233
|
username: user,
|
|
88
234
|
role,
|
|
@@ -91,13 +237,54 @@ function createSnowflakeClient(entry, connectionId) {
|
|
|
91
237
|
privateKey
|
|
92
238
|
});
|
|
93
239
|
await new Promise((resolve, reject) => {
|
|
94
|
-
|
|
240
|
+
connection2.connect((err) => {
|
|
241
|
+
if (err) reject(new Error(`Snowflake connect failed: ${err.message}`));
|
|
242
|
+
else resolve();
|
|
243
|
+
});
|
|
244
|
+
});
|
|
245
|
+
const rows = await new Promise((resolve, reject) => {
|
|
246
|
+
connection2.execute({
|
|
247
|
+
sqlText: sql,
|
|
248
|
+
complete: (err, _stmt, rows2) => {
|
|
249
|
+
if (err) reject(new Error(`Snowflake query failed: ${err.message}`));
|
|
250
|
+
else resolve(rows2 ?? []);
|
|
251
|
+
}
|
|
252
|
+
});
|
|
253
|
+
});
|
|
254
|
+
connection2.destroy((err) => {
|
|
255
|
+
if (err) console.warn(`[connector-client] Snowflake destroy error: ${err.message}`);
|
|
256
|
+
});
|
|
257
|
+
return { rows };
|
|
258
|
+
}
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// src/connector-client/snowflake-pat.ts
|
|
263
|
+
function createSnowflakePatClient(entry, connectionId) {
|
|
264
|
+
const accountIdentifier = resolveEnvVar(entry, "account", connectionId);
|
|
265
|
+
const user = resolveEnvVar(entry, "user", connectionId);
|
|
266
|
+
const role = resolveEnvVar(entry, "role", connectionId);
|
|
267
|
+
const warehouse = resolveEnvVar(entry, "warehouse", connectionId);
|
|
268
|
+
const password = resolveEnvVar(entry, "pat", connectionId);
|
|
269
|
+
return {
|
|
270
|
+
async query(sql) {
|
|
271
|
+
const snowflake = (await import("snowflake-sdk")).default;
|
|
272
|
+
snowflake.configure({ logLevel: "ERROR" });
|
|
273
|
+
const connection2 = snowflake.createConnection({
|
|
274
|
+
account: accountIdentifier,
|
|
275
|
+
username: user,
|
|
276
|
+
role,
|
|
277
|
+
warehouse,
|
|
278
|
+
password
|
|
279
|
+
});
|
|
280
|
+
await new Promise((resolve, reject) => {
|
|
281
|
+
connection2.connect((err) => {
|
|
95
282
|
if (err) reject(new Error(`Snowflake connect failed: ${err.message}`));
|
|
96
283
|
else resolve();
|
|
97
284
|
});
|
|
98
285
|
});
|
|
99
286
|
const rows = await new Promise((resolve, reject) => {
|
|
100
|
-
|
|
287
|
+
connection2.execute({
|
|
101
288
|
sqlText: sql,
|
|
102
289
|
complete: (err, _stmt, rows2) => {
|
|
103
290
|
if (err) reject(new Error(`Snowflake query failed: ${err.message}`));
|
|
@@ -105,7 +292,7 @@ function createSnowflakeClient(entry, connectionId) {
|
|
|
105
292
|
}
|
|
106
293
|
});
|
|
107
294
|
});
|
|
108
|
-
|
|
295
|
+
connection2.destroy((err) => {
|
|
109
296
|
if (err) console.warn(`[connector-client] Snowflake destroy error: ${err.message}`);
|
|
110
297
|
});
|
|
111
298
|
return { rows };
|
|
@@ -308,9 +495,15 @@ function createConnectorRegistry() {
|
|
|
308
495
|
const cached = clientCache.get(connectionId);
|
|
309
496
|
if (cached) return { client: cached, connectorSlug };
|
|
310
497
|
if (connectorSlug === "snowflake") {
|
|
498
|
+
if (entry.connector.authType === "pat") {
|
|
499
|
+
return { client: createSnowflakePatClient(entry, connectionId), connectorSlug };
|
|
500
|
+
}
|
|
311
501
|
return { client: createSnowflakeClient(entry, connectionId), connectorSlug };
|
|
312
502
|
}
|
|
313
503
|
if (connectorSlug === "bigquery") {
|
|
504
|
+
if (entry.connector.authType === "oauth") {
|
|
505
|
+
return { client: createBigQueryOAuthClient(entry, connectionId), connectorSlug };
|
|
506
|
+
}
|
|
314
507
|
return { client: createBigQueryClient(entry, connectionId), connectorSlug };
|
|
315
508
|
}
|
|
316
509
|
if (connectorSlug === "athena") {
|