@squadbase/vite-server 0.0.1-build-7 → 0.0.1-build-9
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 +211 -21
- package/dist/index.d.ts +126 -1
- package/dist/index.js +482 -19
- package/dist/main.js +212 -18
- package/dist/types/data-source.d.ts +5 -5
- package/dist/vite-plugin.js +215 -16
- package/package.json +8 -2
package/dist/index.js
CHANGED
|
@@ -4,12 +4,13 @@ import { cors } from "hono/cors";
|
|
|
4
4
|
import path4 from "path";
|
|
5
5
|
|
|
6
6
|
// src/registry.ts
|
|
7
|
-
import { readdir, readFile, mkdir } from "fs/promises";
|
|
7
|
+
import { readdir, readFile as readFile2, mkdir } from "fs/promises";
|
|
8
8
|
import { watch as fsWatch2 } from "fs";
|
|
9
9
|
import path2 from "path";
|
|
10
10
|
|
|
11
11
|
// src/connector-client/registry.ts
|
|
12
12
|
import { readFileSync, watch as fsWatch } from "fs";
|
|
13
|
+
import { readFile } from "fs/promises";
|
|
13
14
|
import path from "path";
|
|
14
15
|
|
|
15
16
|
// src/connector-client/postgresql.ts
|
|
@@ -37,6 +38,11 @@ function resolveEnvVar(entry, key, connectionId) {
|
|
|
37
38
|
}
|
|
38
39
|
return value;
|
|
39
40
|
}
|
|
41
|
+
function resolveEnvVarOptional(entry, key) {
|
|
42
|
+
const envVarName = entry.envVars[key];
|
|
43
|
+
if (!envVarName) return void 0;
|
|
44
|
+
return process.env[envVarName] || void 0;
|
|
45
|
+
}
|
|
40
46
|
|
|
41
47
|
// src/connector-client/bigquery.ts
|
|
42
48
|
function createBigQueryClient(entry, connectionId) {
|
|
@@ -48,7 +54,7 @@ function createBigQueryClient(entry, connectionId) {
|
|
|
48
54
|
gcpCredentials = JSON.parse(serviceAccountJson);
|
|
49
55
|
} catch {
|
|
50
56
|
throw new Error(
|
|
51
|
-
`BigQuery service account JSON (decoded from base64) is not valid JSON for
|
|
57
|
+
`BigQuery service account JSON (decoded from base64) is not valid JSON for connectionId "${connectionId}"`
|
|
52
58
|
);
|
|
53
59
|
}
|
|
54
60
|
return {
|
|
@@ -105,26 +111,193 @@ function createSnowflakeClient(entry, connectionId) {
|
|
|
105
111
|
};
|
|
106
112
|
}
|
|
107
113
|
|
|
114
|
+
// src/connector-client/mysql.ts
|
|
115
|
+
function createMySQLClient(entry, connectionId) {
|
|
116
|
+
const connectionUrl = resolveEnvVar(entry, "connection-url", connectionId);
|
|
117
|
+
let poolPromise = null;
|
|
118
|
+
function getPool() {
|
|
119
|
+
if (!poolPromise) {
|
|
120
|
+
poolPromise = import("mysql2/promise").then(
|
|
121
|
+
(mysql) => mysql.default.createPool(connectionUrl)
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
return poolPromise;
|
|
125
|
+
}
|
|
126
|
+
return {
|
|
127
|
+
async query(sql, params) {
|
|
128
|
+
const pool = await getPool();
|
|
129
|
+
const [rows] = await pool.execute(sql, params);
|
|
130
|
+
return { rows };
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// src/connector-client/aws-athena.ts
|
|
136
|
+
function createAthenaClient(entry, connectionId) {
|
|
137
|
+
const region = resolveEnvVar(entry, "aws-region", connectionId);
|
|
138
|
+
const accessKeyId = resolveEnvVar(entry, "aws-access-key-id", connectionId);
|
|
139
|
+
const secretAccessKey = resolveEnvVar(entry, "aws-secret-access-key", connectionId);
|
|
140
|
+
const workgroup = resolveEnvVarOptional(entry, "workgroup") ?? "primary";
|
|
141
|
+
const outputLocation = resolveEnvVarOptional(entry, "output-location");
|
|
142
|
+
return {
|
|
143
|
+
async query(sql) {
|
|
144
|
+
const {
|
|
145
|
+
AthenaClient,
|
|
146
|
+
StartQueryExecutionCommand,
|
|
147
|
+
GetQueryExecutionCommand,
|
|
148
|
+
GetQueryResultsCommand
|
|
149
|
+
} = await import("@aws-sdk/client-athena");
|
|
150
|
+
const client = new AthenaClient({
|
|
151
|
+
region,
|
|
152
|
+
credentials: { accessKeyId, secretAccessKey }
|
|
153
|
+
});
|
|
154
|
+
const startParams = {
|
|
155
|
+
QueryString: sql,
|
|
156
|
+
WorkGroup: workgroup
|
|
157
|
+
};
|
|
158
|
+
if (outputLocation) {
|
|
159
|
+
startParams.ResultConfiguration = { OutputLocation: outputLocation };
|
|
160
|
+
}
|
|
161
|
+
const { QueryExecutionId } = await client.send(
|
|
162
|
+
new StartQueryExecutionCommand(startParams)
|
|
163
|
+
);
|
|
164
|
+
if (!QueryExecutionId) throw new Error("Athena: failed to start query execution");
|
|
165
|
+
while (true) {
|
|
166
|
+
const { QueryExecution } = await client.send(
|
|
167
|
+
new GetQueryExecutionCommand({ QueryExecutionId })
|
|
168
|
+
);
|
|
169
|
+
const state = QueryExecution?.Status?.State;
|
|
170
|
+
if (state === "SUCCEEDED") break;
|
|
171
|
+
if (state === "FAILED") {
|
|
172
|
+
throw new Error(
|
|
173
|
+
`Athena query failed: ${QueryExecution?.Status?.StateChangeReason ?? "unknown"}`
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
if (state === "CANCELLED") throw new Error("Athena query was cancelled");
|
|
177
|
+
await new Promise((r) => setTimeout(r, 500));
|
|
178
|
+
}
|
|
179
|
+
const { ResultSet } = await client.send(
|
|
180
|
+
new GetQueryResultsCommand({ QueryExecutionId })
|
|
181
|
+
);
|
|
182
|
+
const resultRows = ResultSet?.Rows ?? [];
|
|
183
|
+
if (resultRows.length === 0) return { rows: [] };
|
|
184
|
+
const headers = resultRows[0].Data?.map((d) => d.VarCharValue ?? "") ?? [];
|
|
185
|
+
const rows = resultRows.slice(1).map((row) => {
|
|
186
|
+
const obj = {};
|
|
187
|
+
row.Data?.forEach((d, i) => {
|
|
188
|
+
obj[headers[i]] = d.VarCharValue ?? null;
|
|
189
|
+
});
|
|
190
|
+
return obj;
|
|
191
|
+
});
|
|
192
|
+
return { rows };
|
|
193
|
+
}
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// src/connector-client/redshift.ts
|
|
198
|
+
function createRedshiftClient(entry, connectionId) {
|
|
199
|
+
const region = resolveEnvVar(entry, "aws-region", connectionId);
|
|
200
|
+
const accessKeyId = resolveEnvVar(entry, "aws-access-key-id", connectionId);
|
|
201
|
+
const secretAccessKey = resolveEnvVar(entry, "aws-secret-access-key", connectionId);
|
|
202
|
+
const database = resolveEnvVar(entry, "database", connectionId);
|
|
203
|
+
const clusterIdentifier = resolveEnvVarOptional(entry, "cluster-identifier");
|
|
204
|
+
const workgroupName = resolveEnvVarOptional(entry, "workgroup-name");
|
|
205
|
+
const secretArn = resolveEnvVarOptional(entry, "secret-arn");
|
|
206
|
+
const dbUser = resolveEnvVarOptional(entry, "db-user");
|
|
207
|
+
return {
|
|
208
|
+
async query(sql) {
|
|
209
|
+
const {
|
|
210
|
+
RedshiftDataClient,
|
|
211
|
+
ExecuteStatementCommand,
|
|
212
|
+
DescribeStatementCommand,
|
|
213
|
+
GetStatementResultCommand
|
|
214
|
+
} = await import("@aws-sdk/client-redshift-data");
|
|
215
|
+
const client = new RedshiftDataClient({
|
|
216
|
+
region,
|
|
217
|
+
credentials: { accessKeyId, secretAccessKey }
|
|
218
|
+
});
|
|
219
|
+
const executeParams = {
|
|
220
|
+
Sql: sql,
|
|
221
|
+
Database: database
|
|
222
|
+
};
|
|
223
|
+
if (clusterIdentifier) executeParams.ClusterIdentifier = clusterIdentifier;
|
|
224
|
+
if (workgroupName) executeParams.WorkgroupName = workgroupName;
|
|
225
|
+
if (secretArn) executeParams.SecretArn = secretArn;
|
|
226
|
+
if (dbUser) executeParams.DbUser = dbUser;
|
|
227
|
+
const { Id } = await client.send(
|
|
228
|
+
new ExecuteStatementCommand(executeParams)
|
|
229
|
+
);
|
|
230
|
+
if (!Id) throw new Error("Redshift: failed to start statement execution");
|
|
231
|
+
while (true) {
|
|
232
|
+
const desc = await client.send(new DescribeStatementCommand({ Id }));
|
|
233
|
+
const status = desc.Status;
|
|
234
|
+
if (status === "FINISHED") break;
|
|
235
|
+
if (status === "FAILED") {
|
|
236
|
+
throw new Error(`Redshift query failed: ${desc.Error ?? "unknown"}`);
|
|
237
|
+
}
|
|
238
|
+
if (status === "ABORTED") throw new Error("Redshift query was aborted");
|
|
239
|
+
await new Promise((r) => setTimeout(r, 500));
|
|
240
|
+
}
|
|
241
|
+
const result = await client.send(new GetStatementResultCommand({ Id }));
|
|
242
|
+
const columns = result.ColumnMetadata?.map((c) => c.name ?? "") ?? [];
|
|
243
|
+
const rows = (result.Records ?? []).map((record) => {
|
|
244
|
+
const obj = {};
|
|
245
|
+
record.forEach((field, i) => {
|
|
246
|
+
const col = columns[i];
|
|
247
|
+
const value = field.stringValue ?? field.longValue ?? field.doubleValue ?? field.booleanValue ?? (field.isNull ? null : field.blobValue ?? null);
|
|
248
|
+
obj[col] = value;
|
|
249
|
+
});
|
|
250
|
+
return obj;
|
|
251
|
+
});
|
|
252
|
+
return { rows };
|
|
253
|
+
}
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// src/connector-client/databricks.ts
|
|
258
|
+
function createDatabricksClient(entry, connectionId) {
|
|
259
|
+
const host = resolveEnvVar(entry, "host", connectionId);
|
|
260
|
+
const httpPath = resolveEnvVar(entry, "http-path", connectionId);
|
|
261
|
+
const token = resolveEnvVar(entry, "token", connectionId);
|
|
262
|
+
return {
|
|
263
|
+
async query(sql) {
|
|
264
|
+
const { DBSQLClient } = await import("@databricks/sql");
|
|
265
|
+
const client = new DBSQLClient();
|
|
266
|
+
await client.connect({ host, path: httpPath, token });
|
|
267
|
+
try {
|
|
268
|
+
const session = await client.openSession();
|
|
269
|
+
try {
|
|
270
|
+
const operation = await session.executeStatement(sql);
|
|
271
|
+
const result = await operation.fetchAll();
|
|
272
|
+
await operation.close();
|
|
273
|
+
return { rows: result };
|
|
274
|
+
} finally {
|
|
275
|
+
await session.close();
|
|
276
|
+
}
|
|
277
|
+
} finally {
|
|
278
|
+
await client.close();
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
|
|
108
284
|
// src/connector-client/registry.ts
|
|
109
285
|
function createConnectorRegistry() {
|
|
110
|
-
let connectionsCache = null;
|
|
111
286
|
const clientCache = /* @__PURE__ */ new Map();
|
|
112
287
|
function getConnectionsFilePath() {
|
|
113
288
|
return process.env.CONNECTIONS_PATH ?? path.join(process.cwd(), ".squadbase/connections.json");
|
|
114
289
|
}
|
|
115
|
-
function
|
|
116
|
-
if (connectionsCache !== null) return connectionsCache;
|
|
290
|
+
async function loadConnections2() {
|
|
117
291
|
const filePath = getConnectionsFilePath();
|
|
118
292
|
try {
|
|
119
|
-
const raw =
|
|
120
|
-
|
|
293
|
+
const raw = await readFile(filePath, "utf-8");
|
|
294
|
+
return JSON.parse(raw);
|
|
121
295
|
} catch {
|
|
122
|
-
|
|
296
|
+
return {};
|
|
123
297
|
}
|
|
124
|
-
return connectionsCache;
|
|
125
298
|
}
|
|
126
299
|
async function getClient2(connectionId) {
|
|
127
|
-
const connections =
|
|
300
|
+
const connections = await loadConnections2();
|
|
128
301
|
const entry = connections[connectionId];
|
|
129
302
|
if (!entry) {
|
|
130
303
|
throw new Error(`connection '${connectionId}' not found in .squadbase/connections.json`);
|
|
@@ -138,6 +311,20 @@ function createConnectorRegistry() {
|
|
|
138
311
|
if (connectorSlug === "bigquery") {
|
|
139
312
|
return { client: createBigQueryClient(entry, connectionId), connectorSlug };
|
|
140
313
|
}
|
|
314
|
+
if (connectorSlug === "athena") {
|
|
315
|
+
return { client: createAthenaClient(entry, connectionId), connectorSlug };
|
|
316
|
+
}
|
|
317
|
+
if (connectorSlug === "redshift") {
|
|
318
|
+
return { client: createRedshiftClient(entry, connectionId), connectorSlug };
|
|
319
|
+
}
|
|
320
|
+
if (connectorSlug === "databricks") {
|
|
321
|
+
return { client: createDatabricksClient(entry, connectionId), connectorSlug };
|
|
322
|
+
}
|
|
323
|
+
if (connectorSlug === "mysql") {
|
|
324
|
+
const client = createMySQLClient(entry, connectionId);
|
|
325
|
+
clientCache.set(connectionId, client);
|
|
326
|
+
return { client, connectorSlug };
|
|
327
|
+
}
|
|
141
328
|
if (connectorSlug === "postgresql" || connectorSlug === "squadbase-db") {
|
|
142
329
|
const urlEnvName = entry.envVars["connection-url"];
|
|
143
330
|
if (!urlEnvName) {
|
|
@@ -154,7 +341,7 @@ function createConnectorRegistry() {
|
|
|
154
341
|
return { client, connectorSlug };
|
|
155
342
|
}
|
|
156
343
|
throw new Error(
|
|
157
|
-
`connector type '${connectorSlug}' is not supported. Supported: "snowflake", "bigquery", "
|
|
344
|
+
`connector type '${connectorSlug}' is not supported as a SQL connector. Supported SQL types: "postgresql", "squadbase-db", "mysql", "snowflake", "bigquery", "athena", "redshift", "databricks". Non-SQL types (airtable, google-analytics, kintone, wix-store, dbt) should be used via TypeScript handlers.`
|
|
158
345
|
);
|
|
159
346
|
}
|
|
160
347
|
function reloadEnvFile2(envPath) {
|
|
@@ -178,19 +365,280 @@ function createConnectorRegistry() {
|
|
|
178
365
|
const envPath = path.join(process.cwd(), ".env");
|
|
179
366
|
try {
|
|
180
367
|
fsWatch(filePath, { persistent: false }, () => {
|
|
181
|
-
console.log("[connector-client] connections.json changed, clearing cache");
|
|
182
|
-
connectionsCache = null;
|
|
368
|
+
console.log("[connector-client] connections.json changed, clearing client cache");
|
|
183
369
|
clientCache.clear();
|
|
184
370
|
setImmediate(() => reloadEnvFile2(envPath));
|
|
185
371
|
});
|
|
186
372
|
} catch {
|
|
187
373
|
}
|
|
188
374
|
}
|
|
189
|
-
return { getClient: getClient2, reloadEnvFile: reloadEnvFile2, watchConnectionsFile: watchConnectionsFile2 };
|
|
375
|
+
return { getClient: getClient2, loadConnections: loadConnections2, reloadEnvFile: reloadEnvFile2, watchConnectionsFile: watchConnectionsFile2 };
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
// src/connector-client/airtable.ts
|
|
379
|
+
function createAirtableClient(entry, slug) {
|
|
380
|
+
const baseId = resolveEnvVar(entry, "base-id", slug);
|
|
381
|
+
const apiKey = resolveEnvVar(entry, "api-key", slug);
|
|
382
|
+
const baseUrl = `https://api.airtable.com/v0/${baseId}`;
|
|
383
|
+
const headers = {
|
|
384
|
+
Authorization: `Bearer ${apiKey}`,
|
|
385
|
+
"Content-Type": "application/json"
|
|
386
|
+
};
|
|
387
|
+
return {
|
|
388
|
+
async listRecords(tableIdOrName, options) {
|
|
389
|
+
const params = new URLSearchParams();
|
|
390
|
+
if (options?.filterByFormula) params.set("filterByFormula", options.filterByFormula);
|
|
391
|
+
if (options?.maxRecords) params.set("maxRecords", String(options.maxRecords));
|
|
392
|
+
if (options?.pageSize) params.set("pageSize", String(options.pageSize));
|
|
393
|
+
if (options?.offset) params.set("offset", options.offset);
|
|
394
|
+
options?.fields?.forEach((f) => params.append("fields[]", f));
|
|
395
|
+
options?.sort?.forEach((s, i) => {
|
|
396
|
+
params.set(`sort[${i}][field]`, s.field);
|
|
397
|
+
if (s.direction) params.set(`sort[${i}][direction]`, s.direction);
|
|
398
|
+
});
|
|
399
|
+
const qs = params.toString();
|
|
400
|
+
const url = `${baseUrl}/${encodeURIComponent(tableIdOrName)}${qs ? `?${qs}` : ""}`;
|
|
401
|
+
const res = await fetch(url, { headers });
|
|
402
|
+
if (!res.ok) throw new Error(`Airtable API error: ${res.status} ${await res.text()}`);
|
|
403
|
+
return res.json();
|
|
404
|
+
},
|
|
405
|
+
async getRecord(tableIdOrName, recordId) {
|
|
406
|
+
const url = `${baseUrl}/${encodeURIComponent(tableIdOrName)}/${recordId}`;
|
|
407
|
+
const res = await fetch(url, { headers });
|
|
408
|
+
if (!res.ok) throw new Error(`Airtable API error: ${res.status} ${await res.text()}`);
|
|
409
|
+
return res.json();
|
|
410
|
+
}
|
|
411
|
+
};
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
// src/connector-client/google-analytics.ts
|
|
415
|
+
function createGoogleAnalyticsClient(entry, slug) {
|
|
416
|
+
const serviceAccountJsonBase64 = resolveEnvVar(entry, "service-account-json-base64", slug);
|
|
417
|
+
const propertyId = resolveEnvVar(entry, "property-id", slug);
|
|
418
|
+
const serviceAccountJson = Buffer.from(serviceAccountJsonBase64, "base64").toString("utf-8");
|
|
419
|
+
let credentials;
|
|
420
|
+
try {
|
|
421
|
+
credentials = JSON.parse(serviceAccountJson);
|
|
422
|
+
} catch {
|
|
423
|
+
throw new Error(
|
|
424
|
+
`Google Analytics service account JSON (decoded from base64) is not valid JSON for slug "${slug}"`
|
|
425
|
+
);
|
|
426
|
+
}
|
|
427
|
+
return {
|
|
428
|
+
async runReport(request) {
|
|
429
|
+
const { BetaAnalyticsDataClient } = await import("@google-analytics/data");
|
|
430
|
+
const client = new BetaAnalyticsDataClient({
|
|
431
|
+
credentials: {
|
|
432
|
+
client_email: credentials.client_email,
|
|
433
|
+
private_key: credentials.private_key
|
|
434
|
+
}
|
|
435
|
+
});
|
|
436
|
+
const response = await client.runReport({
|
|
437
|
+
property: `properties/${propertyId}`,
|
|
438
|
+
dateRanges: request.dateRanges,
|
|
439
|
+
dimensions: request.dimensions,
|
|
440
|
+
metrics: request.metrics,
|
|
441
|
+
limit: request.limit != null ? String(request.limit) : void 0,
|
|
442
|
+
offset: request.offset != null ? String(request.offset) : void 0
|
|
443
|
+
});
|
|
444
|
+
const reportResponse = Array.isArray(response) ? response[0] : response;
|
|
445
|
+
const rawRows = reportResponse.rows;
|
|
446
|
+
const rows = (rawRows ?? []).map((row) => ({
|
|
447
|
+
dimensionValues: (row.dimensionValues ?? []).map((d) => ({
|
|
448
|
+
value: d.value ?? ""
|
|
449
|
+
})),
|
|
450
|
+
metricValues: (row.metricValues ?? []).map((m) => ({
|
|
451
|
+
value: m.value ?? ""
|
|
452
|
+
}))
|
|
453
|
+
}));
|
|
454
|
+
return {
|
|
455
|
+
rows,
|
|
456
|
+
rowCount: Number(reportResponse.rowCount ?? 0)
|
|
457
|
+
};
|
|
458
|
+
}
|
|
459
|
+
};
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
// src/connector-client/kintone.ts
|
|
463
|
+
function createKintoneClient(entry, slug) {
|
|
464
|
+
const baseUrl = resolveEnvVar(entry, "base-url", slug);
|
|
465
|
+
const username = resolveEnvVar(entry, "username", slug);
|
|
466
|
+
const password = resolveEnvVar(entry, "password", slug);
|
|
467
|
+
return {
|
|
468
|
+
async getRecords(appId, options) {
|
|
469
|
+
const { KintoneRestAPIClient } = await import("@kintone/rest-api-client");
|
|
470
|
+
const client = new KintoneRestAPIClient({
|
|
471
|
+
baseUrl,
|
|
472
|
+
auth: { username, password }
|
|
473
|
+
});
|
|
474
|
+
const result = await client.record.getRecords({
|
|
475
|
+
app: appId,
|
|
476
|
+
query: options?.query,
|
|
477
|
+
fields: options?.fields,
|
|
478
|
+
totalCount: options?.totalCount
|
|
479
|
+
});
|
|
480
|
+
return {
|
|
481
|
+
records: result.records,
|
|
482
|
+
totalCount: result.totalCount
|
|
483
|
+
};
|
|
484
|
+
},
|
|
485
|
+
async getRecord(appId, recordId) {
|
|
486
|
+
const { KintoneRestAPIClient } = await import("@kintone/rest-api-client");
|
|
487
|
+
const client = new KintoneRestAPIClient({
|
|
488
|
+
baseUrl,
|
|
489
|
+
auth: { username, password }
|
|
490
|
+
});
|
|
491
|
+
const result = await client.record.getRecord({
|
|
492
|
+
app: appId,
|
|
493
|
+
id: recordId
|
|
494
|
+
});
|
|
495
|
+
return { record: result.record };
|
|
496
|
+
},
|
|
497
|
+
async listApps(options) {
|
|
498
|
+
const { KintoneRestAPIClient } = await import("@kintone/rest-api-client");
|
|
499
|
+
const client = new KintoneRestAPIClient({
|
|
500
|
+
baseUrl,
|
|
501
|
+
auth: { username, password }
|
|
502
|
+
});
|
|
503
|
+
const result = await client.app.getApps({
|
|
504
|
+
ids: options?.ids,
|
|
505
|
+
name: options?.name,
|
|
506
|
+
limit: options?.limit,
|
|
507
|
+
offset: options?.offset
|
|
508
|
+
});
|
|
509
|
+
return { apps: result.apps };
|
|
510
|
+
}
|
|
511
|
+
};
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
// src/connector-client/wix-store.ts
|
|
515
|
+
function createWixStoreClient(entry, slug) {
|
|
516
|
+
const siteId = resolveEnvVar(entry, "site-id", slug);
|
|
517
|
+
const apiKey = resolveEnvVar(entry, "api-key", slug);
|
|
518
|
+
const headers = {
|
|
519
|
+
Authorization: apiKey,
|
|
520
|
+
"wix-site-id": siteId,
|
|
521
|
+
"Content-Type": "application/json"
|
|
522
|
+
};
|
|
523
|
+
return {
|
|
524
|
+
async queryProducts(options) {
|
|
525
|
+
const body = {};
|
|
526
|
+
if (options?.query) body.query = options.query;
|
|
527
|
+
if (options?.limit) {
|
|
528
|
+
body.query = { ...body.query ?? {}, paging: { limit: options.limit, offset: options?.offset ?? 0 } };
|
|
529
|
+
}
|
|
530
|
+
const res = await fetch(
|
|
531
|
+
"https://www.wixapis.com/stores/v1/products/query",
|
|
532
|
+
{ method: "POST", headers, body: JSON.stringify(body) }
|
|
533
|
+
);
|
|
534
|
+
if (!res.ok) throw new Error(`Wix Store API error: ${res.status} ${await res.text()}`);
|
|
535
|
+
const data = await res.json();
|
|
536
|
+
return { products: data.products ?? [], totalResults: data.totalResults ?? 0 };
|
|
537
|
+
},
|
|
538
|
+
async queryOrders(options) {
|
|
539
|
+
const body = {};
|
|
540
|
+
if (options?.query) body.query = options.query;
|
|
541
|
+
if (options?.limit) {
|
|
542
|
+
body.query = { ...body.query ?? {}, paging: { limit: options.limit, offset: options?.offset ?? 0 } };
|
|
543
|
+
}
|
|
544
|
+
const res = await fetch(
|
|
545
|
+
"https://www.wixapis.com/stores/v2/orders/query",
|
|
546
|
+
{ method: "POST", headers, body: JSON.stringify(body) }
|
|
547
|
+
);
|
|
548
|
+
if (!res.ok) throw new Error(`Wix Store API error: ${res.status} ${await res.text()}`);
|
|
549
|
+
const data = await res.json();
|
|
550
|
+
return { orders: data.orders ?? [], totalResults: data.totalResults ?? 0 };
|
|
551
|
+
}
|
|
552
|
+
};
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
// src/connector-client/dbt.ts
|
|
556
|
+
function createDbtClient(entry, slug) {
|
|
557
|
+
const host = resolveEnvVar(entry, "host", slug);
|
|
558
|
+
const prodEnvId = resolveEnvVar(entry, "prod-env-id", slug);
|
|
559
|
+
const token = resolveEnvVar(entry, "token", slug);
|
|
560
|
+
const discoveryUrl = `https://${host}/graphql`;
|
|
561
|
+
const headers = {
|
|
562
|
+
Authorization: `Bearer ${token}`,
|
|
563
|
+
"Content-Type": "application/json"
|
|
564
|
+
};
|
|
565
|
+
async function gqlRequest(query, variables) {
|
|
566
|
+
const res = await fetch(discoveryUrl, {
|
|
567
|
+
method: "POST",
|
|
568
|
+
headers,
|
|
569
|
+
body: JSON.stringify({ query, variables })
|
|
570
|
+
});
|
|
571
|
+
if (!res.ok) throw new Error(`dbt Discovery API error: ${res.status} ${await res.text()}`);
|
|
572
|
+
const json = await res.json();
|
|
573
|
+
if (json.errors) {
|
|
574
|
+
throw new Error(`dbt Discovery API GraphQL error: ${JSON.stringify(json.errors)}`);
|
|
575
|
+
}
|
|
576
|
+
return json.data ?? {};
|
|
577
|
+
}
|
|
578
|
+
return {
|
|
579
|
+
query: gqlRequest,
|
|
580
|
+
async getModels(options) {
|
|
581
|
+
const limit = options?.limit ?? 100;
|
|
582
|
+
const data = await gqlRequest(
|
|
583
|
+
`query ($environmentId: BigInt!, $first: Int) {
|
|
584
|
+
environment(id: $environmentId) {
|
|
585
|
+
applied {
|
|
586
|
+
models(first: $first) {
|
|
587
|
+
edges {
|
|
588
|
+
node {
|
|
589
|
+
uniqueId
|
|
590
|
+
name
|
|
591
|
+
description
|
|
592
|
+
materializedType
|
|
593
|
+
database
|
|
594
|
+
schema
|
|
595
|
+
alias
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
}`,
|
|
602
|
+
{ environmentId: Number(prodEnvId), first: limit }
|
|
603
|
+
);
|
|
604
|
+
const env = data.environment;
|
|
605
|
+
const applied = env?.applied;
|
|
606
|
+
const models = applied?.models;
|
|
607
|
+
return models?.edges.map((e) => e.node) ?? [];
|
|
608
|
+
},
|
|
609
|
+
async getModelByName(uniqueId) {
|
|
610
|
+
const data = await gqlRequest(
|
|
611
|
+
`query ($environmentId: BigInt!, $uniqueId: String!) {
|
|
612
|
+
environment(id: $environmentId) {
|
|
613
|
+
applied {
|
|
614
|
+
modelByUniqueId(uniqueId: $uniqueId) {
|
|
615
|
+
uniqueId
|
|
616
|
+
name
|
|
617
|
+
description
|
|
618
|
+
materializedType
|
|
619
|
+
database
|
|
620
|
+
schema
|
|
621
|
+
alias
|
|
622
|
+
columns {
|
|
623
|
+
name
|
|
624
|
+
description
|
|
625
|
+
type
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
}`,
|
|
631
|
+
{ environmentId: Number(prodEnvId), uniqueId }
|
|
632
|
+
);
|
|
633
|
+
const env = data.environment;
|
|
634
|
+
const applied = env?.applied;
|
|
635
|
+
return applied?.modelByUniqueId ?? null;
|
|
636
|
+
}
|
|
637
|
+
};
|
|
190
638
|
}
|
|
191
639
|
|
|
192
640
|
// src/connector-client/index.ts
|
|
193
|
-
var { getClient, reloadEnvFile, watchConnectionsFile } = createConnectorRegistry();
|
|
641
|
+
var { getClient, loadConnections, reloadEnvFile, watchConnectionsFile } = createConnectorRegistry();
|
|
194
642
|
|
|
195
643
|
// src/registry.ts
|
|
196
644
|
var dataSources = /* @__PURE__ */ new Map();
|
|
@@ -256,7 +704,7 @@ async function initialize() {
|
|
|
256
704
|
const results = await Promise.allSettled(
|
|
257
705
|
jsonFiles.map(async (file) => {
|
|
258
706
|
const slug = file.replace(/\.json$/, "");
|
|
259
|
-
const raw = await
|
|
707
|
+
const raw = await readFile2(`${dirPath}/${file}`, "utf-8");
|
|
260
708
|
const def = JSON.parse(raw);
|
|
261
709
|
if (!def.description) {
|
|
262
710
|
console.warn(`[registry] Skipping ${file}: missing description`);
|
|
@@ -301,10 +749,10 @@ async function initialize() {
|
|
|
301
749
|
_query: sqlDef.query,
|
|
302
750
|
handler: async (runtimeParams) => {
|
|
303
751
|
const { client, connectorSlug } = await getClient(sqlDef.connectionId);
|
|
304
|
-
const
|
|
752
|
+
const isLiteralConnector = connectorSlug === "snowflake" || connectorSlug === "bigquery" || connectorSlug === "athena" || connectorSlug === "redshift" || connectorSlug === "databricks";
|
|
305
753
|
let queryText;
|
|
306
754
|
let queryValues;
|
|
307
|
-
if (
|
|
755
|
+
if (isLiteralConnector) {
|
|
308
756
|
const defaults = new Map(
|
|
309
757
|
(sqlDef.parameters ?? []).map((p) => [p.name, p.default ?? null])
|
|
310
758
|
);
|
|
@@ -322,6 +770,14 @@ async function initialize() {
|
|
|
322
770
|
}
|
|
323
771
|
);
|
|
324
772
|
queryValues = [];
|
|
773
|
+
} else if (connectorSlug === "mysql") {
|
|
774
|
+
const built = buildQuery(
|
|
775
|
+
sqlDef.query,
|
|
776
|
+
sqlDef.parameters ?? [],
|
|
777
|
+
runtimeParams
|
|
778
|
+
);
|
|
779
|
+
queryText = built.text.replace(/\$(\d+)/g, "?");
|
|
780
|
+
queryValues = built.values;
|
|
325
781
|
} else {
|
|
326
782
|
const built = buildQuery(
|
|
327
783
|
sqlDef.query,
|
|
@@ -735,5 +1191,12 @@ app5.get("/healthz", (c) => c.json({ status: "ok" }));
|
|
|
735
1191
|
app5.route("/api", apiApp);
|
|
736
1192
|
var src_default = app5;
|
|
737
1193
|
export {
|
|
738
|
-
|
|
1194
|
+
createAirtableClient,
|
|
1195
|
+
createDbtClient,
|
|
1196
|
+
createGoogleAnalyticsClient,
|
|
1197
|
+
createKintoneClient,
|
|
1198
|
+
createWixStoreClient,
|
|
1199
|
+
src_default as default,
|
|
1200
|
+
getClient,
|
|
1201
|
+
loadConnections
|
|
739
1202
|
};
|