@squadbase/vite-server 0.0.1-build-3 → 0.0.1-build-5
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/index.d.ts +1 -120
- package/dist/index.js +60 -530
- package/dist/main.js +59 -260
- package/dist/types/data-source.d.ts +14 -9
- package/dist/vite-plugin.js +34 -251
- package/package.json +2 -9
- package/dist/cli/index.d.ts +0 -1
- package/dist/cli/index.js +0 -848
package/dist/index.js
CHANGED
|
@@ -26,34 +26,29 @@ function createPostgreSQLClient(connectionString) {
|
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
// src/connector-client/env.ts
|
|
29
|
-
function resolveEnvVar(entry, key,
|
|
29
|
+
function resolveEnvVar(entry, key, connectionId) {
|
|
30
30
|
const envVarName = entry.envVars[key];
|
|
31
31
|
if (!envVarName) {
|
|
32
|
-
throw new Error(`
|
|
32
|
+
throw new Error(`Connection "${connectionId}" is missing envVars mapping for key "${key}"`);
|
|
33
33
|
}
|
|
34
34
|
const value = process.env[envVarName];
|
|
35
35
|
if (!value) {
|
|
36
|
-
throw new Error(`Environment variable "${envVarName}" (for "${
|
|
36
|
+
throw new Error(`Environment variable "${envVarName}" (for connection "${connectionId}", key "${key}") is not set`);
|
|
37
37
|
}
|
|
38
38
|
return value;
|
|
39
39
|
}
|
|
40
|
-
function resolveEnvVarOptional(entry, key) {
|
|
41
|
-
const envVarName = entry.envVars[key];
|
|
42
|
-
if (!envVarName) return void 0;
|
|
43
|
-
return process.env[envVarName] || void 0;
|
|
44
|
-
}
|
|
45
40
|
|
|
46
41
|
// src/connector-client/bigquery.ts
|
|
47
|
-
function createBigQueryClient(entry,
|
|
48
|
-
const projectId = resolveEnvVar(entry, "project-id",
|
|
49
|
-
const serviceAccountJsonBase64 = resolveEnvVar(entry, "service-account-json-base64",
|
|
42
|
+
function createBigQueryClient(entry, connectionId) {
|
|
43
|
+
const projectId = resolveEnvVar(entry, "project-id", connectionId);
|
|
44
|
+
const serviceAccountJsonBase64 = resolveEnvVar(entry, "service-account-json-base64", connectionId);
|
|
50
45
|
const serviceAccountJson = Buffer.from(serviceAccountJsonBase64, "base64").toString("utf-8");
|
|
51
46
|
let gcpCredentials;
|
|
52
47
|
try {
|
|
53
48
|
gcpCredentials = JSON.parse(serviceAccountJson);
|
|
54
49
|
} catch {
|
|
55
50
|
throw new Error(
|
|
56
|
-
`BigQuery service account JSON (decoded from base64) is not valid JSON for
|
|
51
|
+
`BigQuery service account JSON (decoded from base64) is not valid JSON for connection "${connectionId}"`
|
|
57
52
|
);
|
|
58
53
|
}
|
|
59
54
|
return {
|
|
@@ -68,12 +63,12 @@ function createBigQueryClient(entry, slug) {
|
|
|
68
63
|
}
|
|
69
64
|
|
|
70
65
|
// src/connector-client/snowflake.ts
|
|
71
|
-
function createSnowflakeClient(entry,
|
|
72
|
-
const accountIdentifier = resolveEnvVar(entry, "account",
|
|
73
|
-
const user = resolveEnvVar(entry, "user",
|
|
74
|
-
const role = resolveEnvVar(entry, "role",
|
|
75
|
-
const warehouse = resolveEnvVar(entry, "warehouse",
|
|
76
|
-
const privateKeyBase64 = resolveEnvVar(entry, "private-key-base64",
|
|
66
|
+
function createSnowflakeClient(entry, connectionId) {
|
|
67
|
+
const accountIdentifier = resolveEnvVar(entry, "account", connectionId);
|
|
68
|
+
const user = resolveEnvVar(entry, "user", connectionId);
|
|
69
|
+
const role = resolveEnvVar(entry, "role", connectionId);
|
|
70
|
+
const warehouse = resolveEnvVar(entry, "warehouse", connectionId);
|
|
71
|
+
const privateKeyBase64 = resolveEnvVar(entry, "private-key-base64", connectionId);
|
|
77
72
|
const privateKey = Buffer.from(privateKeyBase64, "base64").toString("utf-8");
|
|
78
73
|
return {
|
|
79
74
|
async query(sql) {
|
|
@@ -110,176 +105,6 @@ function createSnowflakeClient(entry, slug) {
|
|
|
110
105
|
};
|
|
111
106
|
}
|
|
112
107
|
|
|
113
|
-
// src/connector-client/mysql.ts
|
|
114
|
-
function createMySQLClient(entry, slug) {
|
|
115
|
-
const connectionUrl = resolveEnvVar(entry, "connection-url", slug);
|
|
116
|
-
let poolPromise = null;
|
|
117
|
-
function getPool() {
|
|
118
|
-
if (!poolPromise) {
|
|
119
|
-
poolPromise = import("mysql2/promise").then(
|
|
120
|
-
(mysql) => mysql.default.createPool(connectionUrl)
|
|
121
|
-
);
|
|
122
|
-
}
|
|
123
|
-
return poolPromise;
|
|
124
|
-
}
|
|
125
|
-
return {
|
|
126
|
-
async query(sql, params) {
|
|
127
|
-
const pool = await getPool();
|
|
128
|
-
const [rows] = await pool.execute(sql, params);
|
|
129
|
-
return { rows };
|
|
130
|
-
}
|
|
131
|
-
};
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
// src/connector-client/aws-athena.ts
|
|
135
|
-
function createAthenaClient(entry, slug) {
|
|
136
|
-
const region = resolveEnvVar(entry, "aws-region", slug);
|
|
137
|
-
const accessKeyId = resolveEnvVar(entry, "aws-access-key-id", slug);
|
|
138
|
-
const secretAccessKey = resolveEnvVar(entry, "aws-secret-access-key", slug);
|
|
139
|
-
const workgroup = resolveEnvVarOptional(entry, "workgroup") ?? "primary";
|
|
140
|
-
const outputLocation = resolveEnvVarOptional(entry, "output-location");
|
|
141
|
-
return {
|
|
142
|
-
async query(sql) {
|
|
143
|
-
const {
|
|
144
|
-
AthenaClient,
|
|
145
|
-
StartQueryExecutionCommand,
|
|
146
|
-
GetQueryExecutionCommand,
|
|
147
|
-
GetQueryResultsCommand
|
|
148
|
-
} = await import("@aws-sdk/client-athena");
|
|
149
|
-
const client = new AthenaClient({
|
|
150
|
-
region,
|
|
151
|
-
credentials: { accessKeyId, secretAccessKey }
|
|
152
|
-
});
|
|
153
|
-
const startParams = {
|
|
154
|
-
QueryString: sql,
|
|
155
|
-
WorkGroup: workgroup
|
|
156
|
-
};
|
|
157
|
-
if (outputLocation) {
|
|
158
|
-
startParams.ResultConfiguration = { OutputLocation: outputLocation };
|
|
159
|
-
}
|
|
160
|
-
const { QueryExecutionId } = await client.send(
|
|
161
|
-
new StartQueryExecutionCommand(startParams)
|
|
162
|
-
);
|
|
163
|
-
if (!QueryExecutionId) throw new Error("Athena: failed to start query execution");
|
|
164
|
-
while (true) {
|
|
165
|
-
const { QueryExecution } = await client.send(
|
|
166
|
-
new GetQueryExecutionCommand({ QueryExecutionId })
|
|
167
|
-
);
|
|
168
|
-
const state = QueryExecution?.Status?.State;
|
|
169
|
-
if (state === "SUCCEEDED") break;
|
|
170
|
-
if (state === "FAILED") {
|
|
171
|
-
throw new Error(
|
|
172
|
-
`Athena query failed: ${QueryExecution?.Status?.StateChangeReason ?? "unknown"}`
|
|
173
|
-
);
|
|
174
|
-
}
|
|
175
|
-
if (state === "CANCELLED") throw new Error("Athena query was cancelled");
|
|
176
|
-
await new Promise((r) => setTimeout(r, 500));
|
|
177
|
-
}
|
|
178
|
-
const { ResultSet } = await client.send(
|
|
179
|
-
new GetQueryResultsCommand({ QueryExecutionId })
|
|
180
|
-
);
|
|
181
|
-
const resultRows = ResultSet?.Rows ?? [];
|
|
182
|
-
if (resultRows.length === 0) return { rows: [] };
|
|
183
|
-
const headers = resultRows[0].Data?.map((d) => d.VarCharValue ?? "") ?? [];
|
|
184
|
-
const rows = resultRows.slice(1).map((row) => {
|
|
185
|
-
const obj = {};
|
|
186
|
-
row.Data?.forEach((d, i) => {
|
|
187
|
-
obj[headers[i]] = d.VarCharValue ?? null;
|
|
188
|
-
});
|
|
189
|
-
return obj;
|
|
190
|
-
});
|
|
191
|
-
return { rows };
|
|
192
|
-
}
|
|
193
|
-
};
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
// src/connector-client/redshift.ts
|
|
197
|
-
function createRedshiftClient(entry, slug) {
|
|
198
|
-
const region = resolveEnvVar(entry, "aws-region", slug);
|
|
199
|
-
const accessKeyId = resolveEnvVar(entry, "aws-access-key-id", slug);
|
|
200
|
-
const secretAccessKey = resolveEnvVar(entry, "aws-secret-access-key", slug);
|
|
201
|
-
const database = resolveEnvVar(entry, "database", slug);
|
|
202
|
-
const clusterIdentifier = resolveEnvVarOptional(entry, "cluster-identifier");
|
|
203
|
-
const workgroupName = resolveEnvVarOptional(entry, "workgroup-name");
|
|
204
|
-
const secretArn = resolveEnvVarOptional(entry, "secret-arn");
|
|
205
|
-
const dbUser = resolveEnvVarOptional(entry, "db-user");
|
|
206
|
-
return {
|
|
207
|
-
async query(sql) {
|
|
208
|
-
const {
|
|
209
|
-
RedshiftDataClient,
|
|
210
|
-
ExecuteStatementCommand,
|
|
211
|
-
DescribeStatementCommand,
|
|
212
|
-
GetStatementResultCommand
|
|
213
|
-
} = await import("@aws-sdk/client-redshift-data");
|
|
214
|
-
const client = new RedshiftDataClient({
|
|
215
|
-
region,
|
|
216
|
-
credentials: { accessKeyId, secretAccessKey }
|
|
217
|
-
});
|
|
218
|
-
const executeParams = {
|
|
219
|
-
Sql: sql,
|
|
220
|
-
Database: database
|
|
221
|
-
};
|
|
222
|
-
if (clusterIdentifier) executeParams.ClusterIdentifier = clusterIdentifier;
|
|
223
|
-
if (workgroupName) executeParams.WorkgroupName = workgroupName;
|
|
224
|
-
if (secretArn) executeParams.SecretArn = secretArn;
|
|
225
|
-
if (dbUser) executeParams.DbUser = dbUser;
|
|
226
|
-
const { Id } = await client.send(
|
|
227
|
-
new ExecuteStatementCommand(executeParams)
|
|
228
|
-
);
|
|
229
|
-
if (!Id) throw new Error("Redshift: failed to start statement execution");
|
|
230
|
-
while (true) {
|
|
231
|
-
const desc = await client.send(new DescribeStatementCommand({ Id }));
|
|
232
|
-
const status = desc.Status;
|
|
233
|
-
if (status === "FINISHED") break;
|
|
234
|
-
if (status === "FAILED") {
|
|
235
|
-
throw new Error(`Redshift query failed: ${desc.Error ?? "unknown"}`);
|
|
236
|
-
}
|
|
237
|
-
if (status === "ABORTED") throw new Error("Redshift query was aborted");
|
|
238
|
-
await new Promise((r) => setTimeout(r, 500));
|
|
239
|
-
}
|
|
240
|
-
const result = await client.send(new GetStatementResultCommand({ Id }));
|
|
241
|
-
const columns = result.ColumnMetadata?.map((c) => c.name ?? "") ?? [];
|
|
242
|
-
const rows = (result.Records ?? []).map((record) => {
|
|
243
|
-
const obj = {};
|
|
244
|
-
record.forEach((field, i) => {
|
|
245
|
-
const col = columns[i];
|
|
246
|
-
const value = field.stringValue ?? field.longValue ?? field.doubleValue ?? field.booleanValue ?? (field.isNull ? null : field.blobValue ?? null);
|
|
247
|
-
obj[col] = value;
|
|
248
|
-
});
|
|
249
|
-
return obj;
|
|
250
|
-
});
|
|
251
|
-
return { rows };
|
|
252
|
-
}
|
|
253
|
-
};
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
// src/connector-client/databricks.ts
|
|
257
|
-
function createDatabricksClient(entry, slug) {
|
|
258
|
-
const host = resolveEnvVar(entry, "host", slug);
|
|
259
|
-
const httpPath = resolveEnvVar(entry, "http-path", slug);
|
|
260
|
-
const token = resolveEnvVar(entry, "token", slug);
|
|
261
|
-
return {
|
|
262
|
-
async query(sql) {
|
|
263
|
-
const { DBSQLClient } = await import("@databricks/sql");
|
|
264
|
-
const client = new DBSQLClient();
|
|
265
|
-
await client.connect({ host, path: httpPath, token });
|
|
266
|
-
try {
|
|
267
|
-
const session = await client.openSession();
|
|
268
|
-
try {
|
|
269
|
-
const operation = await session.executeStatement(sql);
|
|
270
|
-
const result = await operation.fetchAll();
|
|
271
|
-
await operation.close();
|
|
272
|
-
return { rows: result };
|
|
273
|
-
} finally {
|
|
274
|
-
await session.close();
|
|
275
|
-
}
|
|
276
|
-
} finally {
|
|
277
|
-
await client.close();
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
};
|
|
281
|
-
}
|
|
282
|
-
|
|
283
108
|
// src/connector-client/registry.ts
|
|
284
109
|
function createConnectorRegistry() {
|
|
285
110
|
let connectionsCache = null;
|
|
@@ -287,7 +112,7 @@ function createConnectorRegistry() {
|
|
|
287
112
|
function getConnectionsFilePath() {
|
|
288
113
|
return process.env.CONNECTIONS_PATH ?? path.join(process.cwd(), "../../.squadbase/connections.json");
|
|
289
114
|
}
|
|
290
|
-
function
|
|
115
|
+
function loadConnections() {
|
|
291
116
|
if (connectionsCache !== null) return connectionsCache;
|
|
292
117
|
const filePath = getConnectionsFilePath();
|
|
293
118
|
try {
|
|
@@ -298,67 +123,38 @@ function createConnectorRegistry() {
|
|
|
298
123
|
}
|
|
299
124
|
return connectionsCache;
|
|
300
125
|
}
|
|
301
|
-
async function getClient2(
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
const cached2 = clientCache.get(cacheKey);
|
|
305
|
-
if (cached2) return cached2;
|
|
306
|
-
const url = process.env.SQUADBASE_POSTGRESQL_URL;
|
|
307
|
-
if (!url) throw new Error("SQUADBASE_POSTGRESQL_URL environment variable is not set");
|
|
308
|
-
const client = createPostgreSQLClient(url);
|
|
309
|
-
clientCache.set(cacheKey, client);
|
|
310
|
-
return client;
|
|
311
|
-
}
|
|
312
|
-
const cached = clientCache.get(connectorSlug);
|
|
313
|
-
if (cached) return cached;
|
|
314
|
-
const connections = loadConnections2();
|
|
315
|
-
const entry = connections[connectorSlug];
|
|
126
|
+
async function getClient2(connectionId) {
|
|
127
|
+
const connections = loadConnections();
|
|
128
|
+
const entry = connections[connectionId];
|
|
316
129
|
if (!entry) {
|
|
317
|
-
throw new Error(`
|
|
318
|
-
}
|
|
319
|
-
const resolvedType = connectorType ?? entry.connectorType;
|
|
320
|
-
if (!resolvedType) {
|
|
321
|
-
throw new Error(
|
|
322
|
-
`connector type could not be determined for slug '${connectorSlug}'. Specify connectorType in the data-source JSON or in .squadbase/connections.json.`
|
|
323
|
-
);
|
|
130
|
+
throw new Error(`connection '${connectionId}' not found in .squadbase/connections.json`);
|
|
324
131
|
}
|
|
325
|
-
|
|
326
|
-
|
|
132
|
+
const connectorSlug = entry.connector.slug;
|
|
133
|
+
const cached = clientCache.get(connectionId);
|
|
134
|
+
if (cached) return { client: cached, connectorSlug };
|
|
135
|
+
if (connectorSlug === "snowflake") {
|
|
136
|
+
return { client: createSnowflakeClient(entry, connectionId), connectorSlug };
|
|
327
137
|
}
|
|
328
|
-
if (
|
|
329
|
-
return createBigQueryClient(entry, connectorSlug
|
|
138
|
+
if (connectorSlug === "bigquery") {
|
|
139
|
+
return { client: createBigQueryClient(entry, connectionId), connectorSlug };
|
|
330
140
|
}
|
|
331
|
-
if (
|
|
332
|
-
return createAthenaClient(entry, connectorSlug);
|
|
333
|
-
}
|
|
334
|
-
if (resolvedType === "redshift") {
|
|
335
|
-
return createRedshiftClient(entry, connectorSlug);
|
|
336
|
-
}
|
|
337
|
-
if (resolvedType === "databricks") {
|
|
338
|
-
return createDatabricksClient(entry, connectorSlug);
|
|
339
|
-
}
|
|
340
|
-
if (resolvedType === "mysql") {
|
|
341
|
-
const client = createMySQLClient(entry, connectorSlug);
|
|
342
|
-
clientCache.set(connectorSlug, client);
|
|
343
|
-
return client;
|
|
344
|
-
}
|
|
345
|
-
if (resolvedType === "postgresql" || resolvedType === "squadbase-db") {
|
|
141
|
+
if (connectorSlug === "postgresql" || connectorSlug === "squadbase-db") {
|
|
346
142
|
const urlEnvName = entry.envVars["connection-url"];
|
|
347
143
|
if (!urlEnvName) {
|
|
348
|
-
throw new Error(`'connection-url' is not defined in envVars for
|
|
144
|
+
throw new Error(`'connection-url' is not defined in envVars for connection '${connectionId}'`);
|
|
349
145
|
}
|
|
350
146
|
const connectionUrl = process.env[urlEnvName];
|
|
351
147
|
if (!connectionUrl) {
|
|
352
148
|
throw new Error(
|
|
353
|
-
`environment variable '${urlEnvName}' (mapped from
|
|
149
|
+
`environment variable '${urlEnvName}' (mapped from connection '${connectionId}') is not set`
|
|
354
150
|
);
|
|
355
151
|
}
|
|
356
152
|
const client = createPostgreSQLClient(connectionUrl);
|
|
357
|
-
clientCache.set(
|
|
358
|
-
return client;
|
|
153
|
+
clientCache.set(connectionId, client);
|
|
154
|
+
return { client, connectorSlug };
|
|
359
155
|
}
|
|
360
156
|
throw new Error(
|
|
361
|
-
`connector type '${
|
|
157
|
+
`connector type '${connectorSlug}' is not supported. Supported: "snowflake", "bigquery", "postgresql", "squadbase-db"`
|
|
362
158
|
);
|
|
363
159
|
}
|
|
364
160
|
function reloadEnvFile2(envPath) {
|
|
@@ -390,276 +186,15 @@ function createConnectorRegistry() {
|
|
|
390
186
|
} catch {
|
|
391
187
|
}
|
|
392
188
|
}
|
|
393
|
-
return { getClient: getClient2,
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
// src/connector-client/airtable.ts
|
|
397
|
-
function createAirtableClient(entry, slug) {
|
|
398
|
-
const baseId = resolveEnvVar(entry, "base-id", slug);
|
|
399
|
-
const apiKey = resolveEnvVar(entry, "api-key", slug);
|
|
400
|
-
const baseUrl = `https://api.airtable.com/v0/${baseId}`;
|
|
401
|
-
const headers = {
|
|
402
|
-
Authorization: `Bearer ${apiKey}`,
|
|
403
|
-
"Content-Type": "application/json"
|
|
404
|
-
};
|
|
405
|
-
return {
|
|
406
|
-
async listRecords(tableIdOrName, options) {
|
|
407
|
-
const params = new URLSearchParams();
|
|
408
|
-
if (options?.filterByFormula) params.set("filterByFormula", options.filterByFormula);
|
|
409
|
-
if (options?.maxRecords) params.set("maxRecords", String(options.maxRecords));
|
|
410
|
-
if (options?.pageSize) params.set("pageSize", String(options.pageSize));
|
|
411
|
-
if (options?.offset) params.set("offset", options.offset);
|
|
412
|
-
options?.fields?.forEach((f) => params.append("fields[]", f));
|
|
413
|
-
options?.sort?.forEach((s, i) => {
|
|
414
|
-
params.set(`sort[${i}][field]`, s.field);
|
|
415
|
-
if (s.direction) params.set(`sort[${i}][direction]`, s.direction);
|
|
416
|
-
});
|
|
417
|
-
const qs = params.toString();
|
|
418
|
-
const url = `${baseUrl}/${encodeURIComponent(tableIdOrName)}${qs ? `?${qs}` : ""}`;
|
|
419
|
-
const res = await fetch(url, { headers });
|
|
420
|
-
if (!res.ok) throw new Error(`Airtable API error: ${res.status} ${await res.text()}`);
|
|
421
|
-
return res.json();
|
|
422
|
-
},
|
|
423
|
-
async getRecord(tableIdOrName, recordId) {
|
|
424
|
-
const url = `${baseUrl}/${encodeURIComponent(tableIdOrName)}/${recordId}`;
|
|
425
|
-
const res = await fetch(url, { headers });
|
|
426
|
-
if (!res.ok) throw new Error(`Airtable API error: ${res.status} ${await res.text()}`);
|
|
427
|
-
return res.json();
|
|
428
|
-
}
|
|
429
|
-
};
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
// src/connector-client/google-analytics.ts
|
|
433
|
-
function createGoogleAnalyticsClient(entry, slug) {
|
|
434
|
-
const serviceAccountJsonBase64 = resolveEnvVar(entry, "service-account-json-base64", slug);
|
|
435
|
-
const propertyId = resolveEnvVar(entry, "property-id", slug);
|
|
436
|
-
const serviceAccountJson = Buffer.from(serviceAccountJsonBase64, "base64").toString("utf-8");
|
|
437
|
-
let credentials;
|
|
438
|
-
try {
|
|
439
|
-
credentials = JSON.parse(serviceAccountJson);
|
|
440
|
-
} catch {
|
|
441
|
-
throw new Error(
|
|
442
|
-
`Google Analytics service account JSON (decoded from base64) is not valid JSON for slug "${slug}"`
|
|
443
|
-
);
|
|
444
|
-
}
|
|
445
|
-
return {
|
|
446
|
-
async runReport(request) {
|
|
447
|
-
const { BetaAnalyticsDataClient } = await import("@google-analytics/data");
|
|
448
|
-
const client = new BetaAnalyticsDataClient({
|
|
449
|
-
credentials: {
|
|
450
|
-
client_email: credentials.client_email,
|
|
451
|
-
private_key: credentials.private_key
|
|
452
|
-
}
|
|
453
|
-
});
|
|
454
|
-
const response = await client.runReport({
|
|
455
|
-
property: `properties/${propertyId}`,
|
|
456
|
-
dateRanges: request.dateRanges,
|
|
457
|
-
dimensions: request.dimensions,
|
|
458
|
-
metrics: request.metrics,
|
|
459
|
-
limit: request.limit != null ? String(request.limit) : void 0,
|
|
460
|
-
offset: request.offset != null ? String(request.offset) : void 0
|
|
461
|
-
});
|
|
462
|
-
const reportResponse = Array.isArray(response) ? response[0] : response;
|
|
463
|
-
const rawRows = reportResponse.rows;
|
|
464
|
-
const rows = (rawRows ?? []).map((row) => ({
|
|
465
|
-
dimensionValues: (row.dimensionValues ?? []).map((d) => ({
|
|
466
|
-
value: d.value ?? ""
|
|
467
|
-
})),
|
|
468
|
-
metricValues: (row.metricValues ?? []).map((m) => ({
|
|
469
|
-
value: m.value ?? ""
|
|
470
|
-
}))
|
|
471
|
-
}));
|
|
472
|
-
return {
|
|
473
|
-
rows,
|
|
474
|
-
rowCount: Number(reportResponse.rowCount ?? 0)
|
|
475
|
-
};
|
|
476
|
-
}
|
|
477
|
-
};
|
|
478
|
-
}
|
|
479
|
-
|
|
480
|
-
// src/connector-client/kintone.ts
|
|
481
|
-
function createKintoneClient(entry, slug) {
|
|
482
|
-
const baseUrl = resolveEnvVar(entry, "base-url", slug);
|
|
483
|
-
const username = resolveEnvVar(entry, "username", slug);
|
|
484
|
-
const password = resolveEnvVar(entry, "password", slug);
|
|
485
|
-
return {
|
|
486
|
-
async getRecords(appId, options) {
|
|
487
|
-
const { KintoneRestAPIClient } = await import("@kintone/rest-api-client");
|
|
488
|
-
const client = new KintoneRestAPIClient({
|
|
489
|
-
baseUrl,
|
|
490
|
-
auth: { username, password }
|
|
491
|
-
});
|
|
492
|
-
const result = await client.record.getRecords({
|
|
493
|
-
app: appId,
|
|
494
|
-
query: options?.query,
|
|
495
|
-
fields: options?.fields,
|
|
496
|
-
totalCount: options?.totalCount
|
|
497
|
-
});
|
|
498
|
-
return {
|
|
499
|
-
records: result.records,
|
|
500
|
-
totalCount: result.totalCount
|
|
501
|
-
};
|
|
502
|
-
},
|
|
503
|
-
async getRecord(appId, recordId) {
|
|
504
|
-
const { KintoneRestAPIClient } = await import("@kintone/rest-api-client");
|
|
505
|
-
const client = new KintoneRestAPIClient({
|
|
506
|
-
baseUrl,
|
|
507
|
-
auth: { username, password }
|
|
508
|
-
});
|
|
509
|
-
const result = await client.record.getRecord({
|
|
510
|
-
app: appId,
|
|
511
|
-
id: recordId
|
|
512
|
-
});
|
|
513
|
-
return { record: result.record };
|
|
514
|
-
},
|
|
515
|
-
async listApps(options) {
|
|
516
|
-
const { KintoneRestAPIClient } = await import("@kintone/rest-api-client");
|
|
517
|
-
const client = new KintoneRestAPIClient({
|
|
518
|
-
baseUrl,
|
|
519
|
-
auth: { username, password }
|
|
520
|
-
});
|
|
521
|
-
const result = await client.app.getApps({
|
|
522
|
-
ids: options?.ids,
|
|
523
|
-
name: options?.name,
|
|
524
|
-
limit: options?.limit,
|
|
525
|
-
offset: options?.offset
|
|
526
|
-
});
|
|
527
|
-
return { apps: result.apps };
|
|
528
|
-
}
|
|
529
|
-
};
|
|
530
|
-
}
|
|
531
|
-
|
|
532
|
-
// src/connector-client/wix-store.ts
|
|
533
|
-
function createWixStoreClient(entry, slug) {
|
|
534
|
-
const siteId = resolveEnvVar(entry, "site-id", slug);
|
|
535
|
-
const apiKey = resolveEnvVar(entry, "api-key", slug);
|
|
536
|
-
const headers = {
|
|
537
|
-
Authorization: apiKey,
|
|
538
|
-
"wix-site-id": siteId,
|
|
539
|
-
"Content-Type": "application/json"
|
|
540
|
-
};
|
|
541
|
-
return {
|
|
542
|
-
async queryProducts(options) {
|
|
543
|
-
const body = {};
|
|
544
|
-
if (options?.query) body.query = options.query;
|
|
545
|
-
if (options?.limit) {
|
|
546
|
-
body.query = { ...body.query ?? {}, paging: { limit: options.limit, offset: options?.offset ?? 0 } };
|
|
547
|
-
}
|
|
548
|
-
const res = await fetch(
|
|
549
|
-
"https://www.wixapis.com/stores/v1/products/query",
|
|
550
|
-
{ method: "POST", headers, body: JSON.stringify(body) }
|
|
551
|
-
);
|
|
552
|
-
if (!res.ok) throw new Error(`Wix Store API error: ${res.status} ${await res.text()}`);
|
|
553
|
-
const data = await res.json();
|
|
554
|
-
return { products: data.products ?? [], totalResults: data.totalResults ?? 0 };
|
|
555
|
-
},
|
|
556
|
-
async queryOrders(options) {
|
|
557
|
-
const body = {};
|
|
558
|
-
if (options?.query) body.query = options.query;
|
|
559
|
-
if (options?.limit) {
|
|
560
|
-
body.query = { ...body.query ?? {}, paging: { limit: options.limit, offset: options?.offset ?? 0 } };
|
|
561
|
-
}
|
|
562
|
-
const res = await fetch(
|
|
563
|
-
"https://www.wixapis.com/stores/v2/orders/query",
|
|
564
|
-
{ method: "POST", headers, body: JSON.stringify(body) }
|
|
565
|
-
);
|
|
566
|
-
if (!res.ok) throw new Error(`Wix Store API error: ${res.status} ${await res.text()}`);
|
|
567
|
-
const data = await res.json();
|
|
568
|
-
return { orders: data.orders ?? [], totalResults: data.totalResults ?? 0 };
|
|
569
|
-
}
|
|
570
|
-
};
|
|
571
|
-
}
|
|
572
|
-
|
|
573
|
-
// src/connector-client/dbt.ts
|
|
574
|
-
function createDbtClient(entry, slug) {
|
|
575
|
-
const host = resolveEnvVar(entry, "host", slug);
|
|
576
|
-
const prodEnvId = resolveEnvVar(entry, "prod-env-id", slug);
|
|
577
|
-
const token = resolveEnvVar(entry, "token", slug);
|
|
578
|
-
const discoveryUrl = `https://${host}/graphql`;
|
|
579
|
-
const headers = {
|
|
580
|
-
Authorization: `Bearer ${token}`,
|
|
581
|
-
"Content-Type": "application/json"
|
|
582
|
-
};
|
|
583
|
-
async function gqlRequest(query, variables) {
|
|
584
|
-
const res = await fetch(discoveryUrl, {
|
|
585
|
-
method: "POST",
|
|
586
|
-
headers,
|
|
587
|
-
body: JSON.stringify({ query, variables })
|
|
588
|
-
});
|
|
589
|
-
if (!res.ok) throw new Error(`dbt Discovery API error: ${res.status} ${await res.text()}`);
|
|
590
|
-
const json = await res.json();
|
|
591
|
-
if (json.errors) {
|
|
592
|
-
throw new Error(`dbt Discovery API GraphQL error: ${JSON.stringify(json.errors)}`);
|
|
593
|
-
}
|
|
594
|
-
return json.data ?? {};
|
|
595
|
-
}
|
|
596
|
-
return {
|
|
597
|
-
query: gqlRequest,
|
|
598
|
-
async getModels(options) {
|
|
599
|
-
const limit = options?.limit ?? 100;
|
|
600
|
-
const data = await gqlRequest(
|
|
601
|
-
`query ($environmentId: BigInt!, $first: Int) {
|
|
602
|
-
environment(id: $environmentId) {
|
|
603
|
-
applied {
|
|
604
|
-
models(first: $first) {
|
|
605
|
-
edges {
|
|
606
|
-
node {
|
|
607
|
-
uniqueId
|
|
608
|
-
name
|
|
609
|
-
description
|
|
610
|
-
materializedType
|
|
611
|
-
database
|
|
612
|
-
schema
|
|
613
|
-
alias
|
|
614
|
-
}
|
|
615
|
-
}
|
|
616
|
-
}
|
|
617
|
-
}
|
|
618
|
-
}
|
|
619
|
-
}`,
|
|
620
|
-
{ environmentId: Number(prodEnvId), first: limit }
|
|
621
|
-
);
|
|
622
|
-
const env = data.environment;
|
|
623
|
-
const applied = env?.applied;
|
|
624
|
-
const models = applied?.models;
|
|
625
|
-
return models?.edges.map((e) => e.node) ?? [];
|
|
626
|
-
},
|
|
627
|
-
async getModelByName(uniqueId) {
|
|
628
|
-
const data = await gqlRequest(
|
|
629
|
-
`query ($environmentId: BigInt!, $uniqueId: String!) {
|
|
630
|
-
environment(id: $environmentId) {
|
|
631
|
-
applied {
|
|
632
|
-
modelByUniqueId(uniqueId: $uniqueId) {
|
|
633
|
-
uniqueId
|
|
634
|
-
name
|
|
635
|
-
description
|
|
636
|
-
materializedType
|
|
637
|
-
database
|
|
638
|
-
schema
|
|
639
|
-
alias
|
|
640
|
-
columns {
|
|
641
|
-
name
|
|
642
|
-
description
|
|
643
|
-
type
|
|
644
|
-
}
|
|
645
|
-
}
|
|
646
|
-
}
|
|
647
|
-
}
|
|
648
|
-
}`,
|
|
649
|
-
{ environmentId: Number(prodEnvId), uniqueId }
|
|
650
|
-
);
|
|
651
|
-
const env = data.environment;
|
|
652
|
-
const applied = env?.applied;
|
|
653
|
-
return applied?.modelByUniqueId ?? null;
|
|
654
|
-
}
|
|
655
|
-
};
|
|
189
|
+
return { getClient: getClient2, reloadEnvFile: reloadEnvFile2, watchConnectionsFile: watchConnectionsFile2 };
|
|
656
190
|
}
|
|
657
191
|
|
|
658
192
|
// src/connector-client/index.ts
|
|
659
|
-
var { getClient,
|
|
193
|
+
var { getClient, reloadEnvFile, watchConnectionsFile } = createConnectorRegistry();
|
|
660
194
|
|
|
661
195
|
// src/registry.ts
|
|
662
196
|
var dataSources = /* @__PURE__ */ new Map();
|
|
197
|
+
var currentDirPath = "";
|
|
663
198
|
var viteServer = null;
|
|
664
199
|
function validateHandlerPath(dirPath, handlerPath) {
|
|
665
200
|
const absolute = path2.resolve(dirPath, handlerPath);
|
|
@@ -714,6 +249,7 @@ async function initialize() {
|
|
|
714
249
|
);
|
|
715
250
|
dataSources.clear();
|
|
716
251
|
const dirPath = process.env.DATA_SOURCE_DIR || defaultDataSourceDir;
|
|
252
|
+
currentDirPath = dirPath;
|
|
717
253
|
await mkdir(dirPath, { recursive: true });
|
|
718
254
|
const files = await readdir(dirPath);
|
|
719
255
|
const jsonFiles = files.filter((f) => f.endsWith(".json"));
|
|
@@ -726,6 +262,10 @@ async function initialize() {
|
|
|
726
262
|
console.warn(`[registry] Skipping ${file}: missing description`);
|
|
727
263
|
return;
|
|
728
264
|
}
|
|
265
|
+
if (!def.connectionId) {
|
|
266
|
+
console.warn(`[registry] Skipping ${file}: missing connectionId`);
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
729
269
|
if (def.type === "typescript") {
|
|
730
270
|
if (!def.handlerPath) {
|
|
731
271
|
console.warn(`[registry] Skipping ${file}: missing handlerPath`);
|
|
@@ -736,6 +276,7 @@ async function initialize() {
|
|
|
736
276
|
description: def.description,
|
|
737
277
|
parameters: def.parameters ?? [],
|
|
738
278
|
response: def.response,
|
|
279
|
+
connectionId: def.connectionId,
|
|
739
280
|
cacheConfig: def.cache,
|
|
740
281
|
handler: async () => {
|
|
741
282
|
throw new Error("TypeScript handler must be called via _tsHandlerPath");
|
|
@@ -755,14 +296,15 @@ async function initialize() {
|
|
|
755
296
|
description: sqlDef.description,
|
|
756
297
|
parameters: sqlDef.parameters ?? [],
|
|
757
298
|
response: sqlDef.response,
|
|
758
|
-
|
|
299
|
+
connectionId: sqlDef.connectionId,
|
|
759
300
|
cacheConfig: sqlDef.cache,
|
|
301
|
+
_query: sqlDef.query,
|
|
760
302
|
handler: async (runtimeParams) => {
|
|
761
|
-
const client = await getClient(sqlDef.
|
|
762
|
-
const
|
|
303
|
+
const { client, connectorSlug } = await getClient(sqlDef.connectionId);
|
|
304
|
+
const isExternalConnector = connectorSlug === "snowflake" || connectorSlug === "bigquery";
|
|
763
305
|
let queryText;
|
|
764
306
|
let queryValues;
|
|
765
|
-
if (
|
|
307
|
+
if (isExternalConnector) {
|
|
766
308
|
const defaults = new Map(
|
|
767
309
|
(sqlDef.parameters ?? []).map((p) => [p.name, p.default ?? null])
|
|
768
310
|
);
|
|
@@ -780,14 +322,6 @@ async function initialize() {
|
|
|
780
322
|
}
|
|
781
323
|
);
|
|
782
324
|
queryValues = [];
|
|
783
|
-
} else if (sqlDef.connectorType === "mysql") {
|
|
784
|
-
const built = buildQuery(
|
|
785
|
-
sqlDef.query,
|
|
786
|
-
sqlDef.parameters ?? [],
|
|
787
|
-
runtimeParams
|
|
788
|
-
);
|
|
789
|
-
queryText = built.text.replace(/\$(\d+)/g, "?");
|
|
790
|
-
queryValues = built.values;
|
|
791
325
|
} else {
|
|
792
326
|
const built = buildQuery(
|
|
793
327
|
sqlDef.query,
|
|
@@ -838,25 +372,28 @@ function startWatching() {
|
|
|
838
372
|
function getDataSource(slug) {
|
|
839
373
|
return dataSources.get(slug);
|
|
840
374
|
}
|
|
841
|
-
function
|
|
842
|
-
return
|
|
375
|
+
function buildMeta(slug, def) {
|
|
376
|
+
return {
|
|
843
377
|
slug,
|
|
844
378
|
description: def.description,
|
|
379
|
+
type: def._isTypescript ? "typescript" : "sql",
|
|
845
380
|
parameters: def.parameters,
|
|
846
381
|
response: def.response,
|
|
847
|
-
|
|
848
|
-
|
|
382
|
+
query: def._query,
|
|
383
|
+
connectionId: def.connectionId,
|
|
384
|
+
handlerPath: def._tsHandlerPath ? path2.relative(currentDirPath, def._tsHandlerPath) : void 0,
|
|
385
|
+
cache: def.cacheConfig
|
|
386
|
+
};
|
|
387
|
+
}
|
|
388
|
+
function getAllMeta() {
|
|
389
|
+
return Array.from(dataSources.entries()).map(
|
|
390
|
+
([slug, def]) => buildMeta(slug, def)
|
|
391
|
+
);
|
|
849
392
|
}
|
|
850
393
|
function getMeta(slug) {
|
|
851
394
|
const def = dataSources.get(slug);
|
|
852
395
|
if (!def) return void 0;
|
|
853
|
-
return
|
|
854
|
-
slug,
|
|
855
|
-
description: def.description,
|
|
856
|
-
parameters: def.parameters,
|
|
857
|
-
response: def.response,
|
|
858
|
-
connectorSlug: def.connectorSlug
|
|
859
|
-
};
|
|
396
|
+
return buildMeta(slug, def);
|
|
860
397
|
}
|
|
861
398
|
|
|
862
399
|
// src/routes/data-source.ts
|
|
@@ -1198,12 +735,5 @@ app5.get("/healthz", (c) => c.json({ status: "ok" }));
|
|
|
1198
735
|
app5.route("/api", apiApp);
|
|
1199
736
|
var src_default = app5;
|
|
1200
737
|
export {
|
|
1201
|
-
|
|
1202
|
-
createDbtClient,
|
|
1203
|
-
createGoogleAnalyticsClient,
|
|
1204
|
-
createKintoneClient,
|
|
1205
|
-
createWixStoreClient,
|
|
1206
|
-
src_default as default,
|
|
1207
|
-
getClient,
|
|
1208
|
-
loadConnections
|
|
738
|
+
src_default as default
|
|
1209
739
|
};
|