@squadbase/vite-server 0.0.1-build-10 → 0.0.1-build-12
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 +230 -7
- package/dist/index.d.ts +11 -1
- package/dist/index.js +236 -25
- package/dist/main.js +235 -25
- package/dist/types/data-source.d.ts +176 -54
- package/dist/types/data-source.js +71 -0
- package/dist/vite-plugin.js +215 -4
- package/package.json +3 -2
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,7 @@ function createSnowflakeClient(entry, connectionId) {
|
|
|
179
325
|
}
|
|
180
326
|
});
|
|
181
327
|
});
|
|
182
|
-
|
|
328
|
+
connection2.destroy((err) => {
|
|
183
329
|
if (err) console.warn(`[connector-client] Snowflake destroy error: ${err.message}`);
|
|
184
330
|
});
|
|
185
331
|
return { rows };
|
|
@@ -385,6 +531,9 @@ function createConnectorRegistry() {
|
|
|
385
531
|
return { client: createSnowflakeClient(entry, connectionId), connectorSlug };
|
|
386
532
|
}
|
|
387
533
|
if (connectorSlug === "bigquery") {
|
|
534
|
+
if (entry.connector.authType === "oauth") {
|
|
535
|
+
return { client: createBigQueryOAuthClient(entry, connectionId), connectorSlug };
|
|
536
|
+
}
|
|
388
537
|
return { client: createBigQueryClient(entry, connectionId), connectorSlug };
|
|
389
538
|
}
|
|
390
539
|
if (connectorSlug === "athena") {
|
|
@@ -469,6 +618,70 @@ import path3 from "path";
|
|
|
469
618
|
import { readdir, readFile as readFile2, mkdir } from "fs/promises";
|
|
470
619
|
import { watch as fsWatch2 } from "fs";
|
|
471
620
|
import path2 from "path";
|
|
621
|
+
|
|
622
|
+
// src/types/data-source.ts
|
|
623
|
+
import { z } from "zod";
|
|
624
|
+
var parameterMetaSchema = z.object({
|
|
625
|
+
name: z.string(),
|
|
626
|
+
type: z.enum(["string", "number", "boolean"]),
|
|
627
|
+
description: z.string(),
|
|
628
|
+
required: z.boolean().optional(),
|
|
629
|
+
default: z.union([z.string(), z.number(), z.boolean()]).optional()
|
|
630
|
+
});
|
|
631
|
+
var dataSourceCacheConfigSchema = z.object({
|
|
632
|
+
ttl: z.number(),
|
|
633
|
+
staleWhileRevalidate: z.boolean().optional()
|
|
634
|
+
});
|
|
635
|
+
var dataSourceSchemaObjectSchema = z.lazy(
|
|
636
|
+
() => z.object({
|
|
637
|
+
type: z.enum(["string", "number", "integer", "boolean", "object", "array", "null"]).optional(),
|
|
638
|
+
format: z.string().optional(),
|
|
639
|
+
description: z.string().optional(),
|
|
640
|
+
nullable: z.boolean().optional(),
|
|
641
|
+
enum: z.array(z.union([z.string(), z.number(), z.boolean(), z.null()])).optional(),
|
|
642
|
+
items: dataSourceSchemaObjectSchema.optional(),
|
|
643
|
+
properties: z.record(z.string(), dataSourceSchemaObjectSchema).optional(),
|
|
644
|
+
required: z.array(z.string()).optional(),
|
|
645
|
+
additionalProperties: z.union([z.boolean(), dataSourceSchemaObjectSchema]).optional(),
|
|
646
|
+
minimum: z.number().optional(),
|
|
647
|
+
maximum: z.number().optional(),
|
|
648
|
+
minLength: z.number().optional(),
|
|
649
|
+
maxLength: z.number().optional(),
|
|
650
|
+
pattern: z.string().optional()
|
|
651
|
+
})
|
|
652
|
+
);
|
|
653
|
+
var dataSourceMediaTypeSchema = z.object({
|
|
654
|
+
schema: dataSourceSchemaObjectSchema.optional(),
|
|
655
|
+
example: z.unknown().optional()
|
|
656
|
+
});
|
|
657
|
+
var dataSourceResponseSchema = z.object({
|
|
658
|
+
description: z.string().optional(),
|
|
659
|
+
defaultContentType: z.string().optional(),
|
|
660
|
+
content: z.record(z.string(), dataSourceMediaTypeSchema).optional()
|
|
661
|
+
});
|
|
662
|
+
var jsonBaseFields = {
|
|
663
|
+
description: z.string(),
|
|
664
|
+
parameters: z.array(parameterMetaSchema).optional(),
|
|
665
|
+
response: dataSourceResponseSchema.optional(),
|
|
666
|
+
cache: dataSourceCacheConfigSchema.optional()
|
|
667
|
+
};
|
|
668
|
+
var jsonSqlDataSourceSchema = z.object({
|
|
669
|
+
...jsonBaseFields,
|
|
670
|
+
type: z.literal("sql").optional(),
|
|
671
|
+
query: z.string(),
|
|
672
|
+
connectionId: z.string()
|
|
673
|
+
});
|
|
674
|
+
var jsonTypeScriptDataSourceSchema = z.object({
|
|
675
|
+
...jsonBaseFields,
|
|
676
|
+
type: z.literal("typescript"),
|
|
677
|
+
handlerPath: z.string()
|
|
678
|
+
});
|
|
679
|
+
var anyJsonDataSourceSchema = z.union([
|
|
680
|
+
jsonTypeScriptDataSourceSchema,
|
|
681
|
+
jsonSqlDataSourceSchema
|
|
682
|
+
]);
|
|
683
|
+
|
|
684
|
+
// src/registry.ts
|
|
472
685
|
function buildQuery(queryTemplate, parameterMeta, runtimeParams) {
|
|
473
686
|
const defaults = new Map(
|
|
474
687
|
parameterMeta.map((p) => [p.name, p.default ?? null])
|
|
@@ -605,7 +818,17 @@ async function runDataSource(slug, dirPath, params, limit) {
|
|
|
605
818
|
let def;
|
|
606
819
|
try {
|
|
607
820
|
const raw = await readFile3(jsonPath, "utf-8");
|
|
608
|
-
|
|
821
|
+
const parsed = anyJsonDataSourceSchema.safeParse(JSON.parse(raw));
|
|
822
|
+
if (!parsed.success) {
|
|
823
|
+
return {
|
|
824
|
+
slug,
|
|
825
|
+
rows: [],
|
|
826
|
+
rowCount: 0,
|
|
827
|
+
durationMs: 0,
|
|
828
|
+
error: new Error(`Invalid data source definition: ${parsed.error.message}`)
|
|
829
|
+
};
|
|
830
|
+
}
|
|
831
|
+
def = parsed.data;
|
|
609
832
|
} catch {
|
|
610
833
|
return {
|
|
611
834
|
slug,
|
|
@@ -811,8 +1034,8 @@ Total: ${results.length}, Failed: ${failed}`);
|
|
|
811
1034
|
let paramMeta = [];
|
|
812
1035
|
try {
|
|
813
1036
|
const raw = await readFile4(jsonPath, "utf-8");
|
|
814
|
-
const
|
|
815
|
-
paramMeta =
|
|
1037
|
+
const parsed = anyJsonDataSourceSchema.safeParse(JSON.parse(raw));
|
|
1038
|
+
if (parsed.success) paramMeta = parsed.data.parameters ?? [];
|
|
816
1039
|
} catch {
|
|
817
1040
|
}
|
|
818
1041
|
const interactiveParams = await inputParameters2(paramMeta);
|
package/dist/index.d.ts
CHANGED
|
@@ -126,6 +126,16 @@ declare const getClient: (connectionId: string) => Promise<{
|
|
|
126
126
|
}>;
|
|
127
127
|
declare const loadConnections: () => Promise<ConnectionsMap>;
|
|
128
128
|
|
|
129
|
+
type ConnectionFetchOptions = {
|
|
130
|
+
method?: "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
|
|
131
|
+
headers?: Record<string, string>;
|
|
132
|
+
body?: unknown;
|
|
133
|
+
timeoutMs?: number;
|
|
134
|
+
};
|
|
135
|
+
declare function connection(connectionId: string): {
|
|
136
|
+
fetch(url: string, options?: ConnectionFetchOptions): Promise<Response>;
|
|
137
|
+
};
|
|
138
|
+
|
|
129
139
|
declare const app: Hono<hono_types.BlankEnv, hono_types.BlankSchema, "/">;
|
|
130
140
|
|
|
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 };
|
|
141
|
+
export { type AirtableClient, type AirtableRecord, type ConnectionEntry, type ConnectionFetchOptions, type ConnectionsMap, type DatabaseClient, type DbtClient, type GoogleAnalyticsClient, type KintoneClient, type WixStoreClient, connection, createAirtableClient, createDbtClient, createGoogleAnalyticsClient, createKintoneClient, createWixStoreClient, app as default, getClient, loadConnections };
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
2
|
import { Hono as Hono5 } from "hono";
|
|
3
|
+
import { contextStorage } from "hono/context-storage";
|
|
3
4
|
import { cors } from "hono/cors";
|
|
4
5
|
import path4 from "path";
|
|
5
6
|
|
|
@@ -68,6 +69,152 @@ function createBigQueryClient(entry, connectionId) {
|
|
|
68
69
|
};
|
|
69
70
|
}
|
|
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
|
+
|
|
71
218
|
// src/connector-client/snowflake.ts
|
|
72
219
|
function createSnowflakeClient(entry, connectionId) {
|
|
73
220
|
const accountIdentifier = resolveEnvVar(entry, "account", connectionId);
|
|
@@ -80,7 +227,7 @@ function createSnowflakeClient(entry, connectionId) {
|
|
|
80
227
|
async query(sql) {
|
|
81
228
|
const snowflake = (await import("snowflake-sdk")).default;
|
|
82
229
|
snowflake.configure({ logLevel: "ERROR" });
|
|
83
|
-
const
|
|
230
|
+
const connection2 = snowflake.createConnection({
|
|
84
231
|
account: accountIdentifier,
|
|
85
232
|
username: user,
|
|
86
233
|
role,
|
|
@@ -89,13 +236,13 @@ function createSnowflakeClient(entry, connectionId) {
|
|
|
89
236
|
privateKey
|
|
90
237
|
});
|
|
91
238
|
await new Promise((resolve, reject) => {
|
|
92
|
-
|
|
239
|
+
connection2.connect((err) => {
|
|
93
240
|
if (err) reject(new Error(`Snowflake connect failed: ${err.message}`));
|
|
94
241
|
else resolve();
|
|
95
242
|
});
|
|
96
243
|
});
|
|
97
244
|
const rows = await new Promise((resolve, reject) => {
|
|
98
|
-
|
|
245
|
+
connection2.execute({
|
|
99
246
|
sqlText: sql,
|
|
100
247
|
complete: (err, _stmt, rows2) => {
|
|
101
248
|
if (err) reject(new Error(`Snowflake query failed: ${err.message}`));
|
|
@@ -103,7 +250,7 @@ function createSnowflakeClient(entry, connectionId) {
|
|
|
103
250
|
}
|
|
104
251
|
});
|
|
105
252
|
});
|
|
106
|
-
|
|
253
|
+
connection2.destroy((err) => {
|
|
107
254
|
if (err) console.warn(`[connector-client] Snowflake destroy error: ${err.message}`);
|
|
108
255
|
});
|
|
109
256
|
return { rows };
|
|
@@ -309,6 +456,9 @@ function createConnectorRegistry() {
|
|
|
309
456
|
return { client: createSnowflakeClient(entry, connectionId), connectorSlug };
|
|
310
457
|
}
|
|
311
458
|
if (connectorSlug === "bigquery") {
|
|
459
|
+
if (entry.connector.authType === "oauth") {
|
|
460
|
+
return { client: createBigQueryOAuthClient(entry, connectionId), connectorSlug };
|
|
461
|
+
}
|
|
312
462
|
return { client: createBigQueryClient(entry, connectionId), connectorSlug };
|
|
313
463
|
}
|
|
314
464
|
if (connectorSlug === "athena") {
|
|
@@ -640,6 +790,68 @@ function createDbtClient(entry, slug) {
|
|
|
640
790
|
// src/connector-client/index.ts
|
|
641
791
|
var { getClient, loadConnections, reloadEnvFile, watchConnectionsFile } = createConnectorRegistry();
|
|
642
792
|
|
|
793
|
+
// src/types/data-source.ts
|
|
794
|
+
import { z } from "zod";
|
|
795
|
+
var parameterMetaSchema = z.object({
|
|
796
|
+
name: z.string(),
|
|
797
|
+
type: z.enum(["string", "number", "boolean"]),
|
|
798
|
+
description: z.string(),
|
|
799
|
+
required: z.boolean().optional(),
|
|
800
|
+
default: z.union([z.string(), z.number(), z.boolean()]).optional()
|
|
801
|
+
});
|
|
802
|
+
var dataSourceCacheConfigSchema = z.object({
|
|
803
|
+
ttl: z.number(),
|
|
804
|
+
staleWhileRevalidate: z.boolean().optional()
|
|
805
|
+
});
|
|
806
|
+
var dataSourceSchemaObjectSchema = z.lazy(
|
|
807
|
+
() => z.object({
|
|
808
|
+
type: z.enum(["string", "number", "integer", "boolean", "object", "array", "null"]).optional(),
|
|
809
|
+
format: z.string().optional(),
|
|
810
|
+
description: z.string().optional(),
|
|
811
|
+
nullable: z.boolean().optional(),
|
|
812
|
+
enum: z.array(z.union([z.string(), z.number(), z.boolean(), z.null()])).optional(),
|
|
813
|
+
items: dataSourceSchemaObjectSchema.optional(),
|
|
814
|
+
properties: z.record(z.string(), dataSourceSchemaObjectSchema).optional(),
|
|
815
|
+
required: z.array(z.string()).optional(),
|
|
816
|
+
additionalProperties: z.union([z.boolean(), dataSourceSchemaObjectSchema]).optional(),
|
|
817
|
+
minimum: z.number().optional(),
|
|
818
|
+
maximum: z.number().optional(),
|
|
819
|
+
minLength: z.number().optional(),
|
|
820
|
+
maxLength: z.number().optional(),
|
|
821
|
+
pattern: z.string().optional()
|
|
822
|
+
})
|
|
823
|
+
);
|
|
824
|
+
var dataSourceMediaTypeSchema = z.object({
|
|
825
|
+
schema: dataSourceSchemaObjectSchema.optional(),
|
|
826
|
+
example: z.unknown().optional()
|
|
827
|
+
});
|
|
828
|
+
var dataSourceResponseSchema = z.object({
|
|
829
|
+
description: z.string().optional(),
|
|
830
|
+
defaultContentType: z.string().optional(),
|
|
831
|
+
content: z.record(z.string(), dataSourceMediaTypeSchema).optional()
|
|
832
|
+
});
|
|
833
|
+
var jsonBaseFields = {
|
|
834
|
+
description: z.string(),
|
|
835
|
+
parameters: z.array(parameterMetaSchema).optional(),
|
|
836
|
+
response: dataSourceResponseSchema.optional(),
|
|
837
|
+
cache: dataSourceCacheConfigSchema.optional()
|
|
838
|
+
};
|
|
839
|
+
var jsonSqlDataSourceSchema = z.object({
|
|
840
|
+
...jsonBaseFields,
|
|
841
|
+
type: z.literal("sql").optional(),
|
|
842
|
+
query: z.string(),
|
|
843
|
+
connectionId: z.string()
|
|
844
|
+
});
|
|
845
|
+
var jsonTypeScriptDataSourceSchema = z.object({
|
|
846
|
+
...jsonBaseFields,
|
|
847
|
+
type: z.literal("typescript"),
|
|
848
|
+
handlerPath: z.string()
|
|
849
|
+
});
|
|
850
|
+
var anyJsonDataSourceSchema = z.union([
|
|
851
|
+
jsonTypeScriptDataSourceSchema,
|
|
852
|
+
jsonSqlDataSourceSchema
|
|
853
|
+
]);
|
|
854
|
+
|
|
643
855
|
// src/registry.ts
|
|
644
856
|
var dataSources = /* @__PURE__ */ new Map();
|
|
645
857
|
var currentDirPath = "";
|
|
@@ -705,26 +917,18 @@ async function initialize() {
|
|
|
705
917
|
jsonFiles.map(async (file) => {
|
|
706
918
|
const slug = file.replace(/\.json$/, "");
|
|
707
919
|
const raw = await readFile2(`${dirPath}/${file}`, "utf-8");
|
|
708
|
-
const
|
|
709
|
-
if (!
|
|
710
|
-
console.warn(`[registry] Skipping ${file}:
|
|
711
|
-
return;
|
|
712
|
-
}
|
|
713
|
-
if (!def.connectionId) {
|
|
714
|
-
console.warn(`[registry] Skipping ${file}: missing connectionId`);
|
|
920
|
+
const parsed = anyJsonDataSourceSchema.safeParse(JSON.parse(raw));
|
|
921
|
+
if (!parsed.success) {
|
|
922
|
+
console.warn(`[registry] Skipping ${file}: ${parsed.error.message}`);
|
|
715
923
|
return;
|
|
716
924
|
}
|
|
925
|
+
const def = parsed.data;
|
|
717
926
|
if (def.type === "typescript") {
|
|
718
|
-
if (!def.handlerPath) {
|
|
719
|
-
console.warn(`[registry] Skipping ${file}: missing handlerPath`);
|
|
720
|
-
return;
|
|
721
|
-
}
|
|
722
927
|
const absoluteHandlerPath = validateHandlerPath(dirPath, def.handlerPath);
|
|
723
928
|
const dataSourceDef = {
|
|
724
929
|
description: def.description,
|
|
725
930
|
parameters: def.parameters ?? [],
|
|
726
931
|
response: def.response,
|
|
727
|
-
connectionId: def.connectionId,
|
|
728
932
|
cacheConfig: def.cache,
|
|
729
933
|
handler: async () => {
|
|
730
934
|
throw new Error("TypeScript handler must be called via _tsHandlerPath");
|
|
@@ -736,10 +940,6 @@ async function initialize() {
|
|
|
736
940
|
console.log(`[registry] registered (typescript): ${slug}`);
|
|
737
941
|
} else {
|
|
738
942
|
const sqlDef = def;
|
|
739
|
-
if (!sqlDef.query) {
|
|
740
|
-
console.warn(`[registry] Skipping ${file}: missing query`);
|
|
741
|
-
return;
|
|
742
|
-
}
|
|
743
943
|
const dataSourceDef = {
|
|
744
944
|
description: sqlDef.description,
|
|
745
945
|
parameters: sqlDef.parameters ?? [],
|
|
@@ -829,17 +1029,26 @@ function getDataSource(slug) {
|
|
|
829
1029
|
return dataSources.get(slug);
|
|
830
1030
|
}
|
|
831
1031
|
function buildMeta(slug, def) {
|
|
832
|
-
|
|
1032
|
+
const base = {
|
|
833
1033
|
slug,
|
|
834
1034
|
description: def.description,
|
|
835
|
-
type: def._isTypescript ? "typescript" : "sql",
|
|
836
1035
|
parameters: def.parameters,
|
|
837
1036
|
response: def.response,
|
|
838
|
-
query: def._query,
|
|
839
|
-
connectionId: def.connectionId,
|
|
840
|
-
handlerPath: def._tsHandlerPath ? path2.relative(currentDirPath, def._tsHandlerPath) : void 0,
|
|
841
1037
|
cache: def.cacheConfig
|
|
842
1038
|
};
|
|
1039
|
+
if (def._isTypescript) {
|
|
1040
|
+
return {
|
|
1041
|
+
...base,
|
|
1042
|
+
type: "typescript",
|
|
1043
|
+
handlerPath: path2.relative(currentDirPath, def._tsHandlerPath)
|
|
1044
|
+
};
|
|
1045
|
+
}
|
|
1046
|
+
return {
|
|
1047
|
+
...base,
|
|
1048
|
+
type: "sql",
|
|
1049
|
+
connectionId: def.connectionId,
|
|
1050
|
+
query: def._query
|
|
1051
|
+
};
|
|
843
1052
|
}
|
|
844
1053
|
function getAllMeta() {
|
|
845
1054
|
return Array.from(dataSources.entries()).map(
|
|
@@ -1177,6 +1386,7 @@ var pages_default = app4;
|
|
|
1177
1386
|
|
|
1178
1387
|
// src/index.ts
|
|
1179
1388
|
var apiApp = new Hono5();
|
|
1389
|
+
apiApp.use("/*", contextStorage());
|
|
1180
1390
|
apiApp.use("/*", cors());
|
|
1181
1391
|
apiApp.route("/data-source", data_source_default);
|
|
1182
1392
|
apiApp.route("/data-source-meta", data_source_meta_default);
|
|
@@ -1191,6 +1401,7 @@ app5.get("/healthz", (c) => c.json({ status: "ok" }));
|
|
|
1191
1401
|
app5.route("/api", apiApp);
|
|
1192
1402
|
var src_default = app5;
|
|
1193
1403
|
export {
|
|
1404
|
+
connection,
|
|
1194
1405
|
createAirtableClient,
|
|
1195
1406
|
createDbtClient,
|
|
1196
1407
|
createGoogleAnalyticsClient,
|