@squadbase/vite-server 0.0.1-build-13 → 0.0.1-build-14

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 CHANGED
@@ -88,19 +88,7 @@ import { readFile as readFile4 } from "fs/promises";
88
88
  import { readFileSync, watch as fsWatch } from "fs";
89
89
  import { readFile } from "fs/promises";
90
90
  import path from "path";
91
-
92
- // src/connector-client/postgresql.ts
93
- import pg from "pg";
94
- var { Pool } = pg;
95
- function createPostgreSQLClient(connectionString) {
96
- const pool = new Pool({ connectionString, ssl: { rejectUnauthorized: false } });
97
- return {
98
- async query(sql, params) {
99
- const result = await pool.query(sql, params);
100
- return { rows: result.rows };
101
- }
102
- };
103
- }
91
+ import { connectors } from "@squadbase/connectors";
104
92
 
105
93
  // src/connector-client/env.ts
106
94
  function resolveEnvVar(entry, key, connectionId) {
@@ -120,433 +108,8 @@ function resolveEnvVarOptional(entry, key) {
120
108
  return process.env[envVarName] || void 0;
121
109
  }
122
110
 
123
- // src/connector-client/bigquery.ts
124
- function createBigQueryClient(entry, connectionId) {
125
- const projectId = resolveEnvVar(entry, "project-id", connectionId);
126
- const serviceAccountJsonBase64 = resolveEnvVar(entry, "service-account-key-json-base64", connectionId);
127
- const serviceAccountJson = Buffer.from(serviceAccountJsonBase64, "base64").toString("utf-8");
128
- let gcpCredentials;
129
- try {
130
- gcpCredentials = JSON.parse(serviceAccountJson);
131
- } catch {
132
- throw new Error(
133
- `BigQuery service account JSON (decoded from base64) is not valid JSON for connectionId "${connectionId}"`
134
- );
135
- }
136
- return {
137
- async query(sql) {
138
- const { BigQuery } = await import("@google-cloud/bigquery");
139
- const bq = new BigQuery({ projectId, credentials: gcpCredentials });
140
- const [job] = await bq.createQueryJob({ query: sql });
141
- const [allRows] = await job.getQueryResults({ timeoutMs: 3e4 });
142
- return { rows: allRows };
143
- }
144
- };
145
- }
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
-
293
- // src/connector-client/snowflake.ts
294
- function createSnowflakeClient(entry, connectionId) {
295
- const accountIdentifier = resolveEnvVar(entry, "account", connectionId);
296
- const user = resolveEnvVar(entry, "user", connectionId);
297
- const role = resolveEnvVar(entry, "role", connectionId);
298
- const warehouse = resolveEnvVar(entry, "warehouse", connectionId);
299
- const privateKeyBase64 = resolveEnvVar(entry, "private-key-base64", connectionId);
300
- const privateKey = Buffer.from(privateKeyBase64, "base64").toString("utf-8");
301
- return {
302
- async query(sql) {
303
- const snowflake = (await import("snowflake-sdk")).default;
304
- snowflake.configure({ logLevel: "ERROR" });
305
- const connection2 = snowflake.createConnection({
306
- account: accountIdentifier,
307
- username: user,
308
- role,
309
- warehouse,
310
- authenticator: "SNOWFLAKE_JWT",
311
- privateKey
312
- });
313
- await new Promise((resolve, reject) => {
314
- connection2.connect((err) => {
315
- if (err) reject(new Error(`Snowflake connect failed: ${err.message}`));
316
- else resolve();
317
- });
318
- });
319
- const rows = await new Promise((resolve, reject) => {
320
- connection2.execute({
321
- sqlText: sql,
322
- complete: (err, _stmt, rows2) => {
323
- if (err) reject(new Error(`Snowflake query failed: ${err.message}`));
324
- else resolve(rows2 ?? []);
325
- }
326
- });
327
- });
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) => {
370
- if (err) console.warn(`[connector-client] Snowflake destroy error: ${err.message}`);
371
- });
372
- return { rows };
373
- }
374
- };
375
- }
376
-
377
- // src/connector-client/mysql.ts
378
- function createMySQLClient(entry, connectionId) {
379
- const connectionUrl = resolveEnvVar(entry, "connection-url", connectionId);
380
- let poolPromise = null;
381
- function getPool() {
382
- if (!poolPromise) {
383
- poolPromise = import("mysql2/promise").then(
384
- (mysql) => mysql.default.createPool(connectionUrl)
385
- );
386
- }
387
- return poolPromise;
388
- }
389
- return {
390
- async query(sql, params) {
391
- const pool = await getPool();
392
- const [rows] = await pool.execute(sql, params);
393
- return { rows };
394
- }
395
- };
396
- }
397
-
398
- // src/connector-client/aws-athena.ts
399
- function createAthenaClient(entry, connectionId) {
400
- const region = resolveEnvVar(entry, "aws-region", connectionId);
401
- const accessKeyId = resolveEnvVar(entry, "aws-access-key-id", connectionId);
402
- const secretAccessKey = resolveEnvVar(entry, "aws-secret-access-key", connectionId);
403
- const workgroup = resolveEnvVarOptional(entry, "workgroup") ?? "primary";
404
- const outputLocation = resolveEnvVarOptional(entry, "output-location");
405
- return {
406
- async query(sql) {
407
- const {
408
- AthenaClient,
409
- StartQueryExecutionCommand,
410
- GetQueryExecutionCommand,
411
- GetQueryResultsCommand
412
- } = await import("@aws-sdk/client-athena");
413
- const client = new AthenaClient({
414
- region,
415
- credentials: { accessKeyId, secretAccessKey }
416
- });
417
- const startParams = {
418
- QueryString: sql,
419
- WorkGroup: workgroup
420
- };
421
- if (outputLocation) {
422
- startParams.ResultConfiguration = { OutputLocation: outputLocation };
423
- }
424
- const { QueryExecutionId } = await client.send(
425
- new StartQueryExecutionCommand(startParams)
426
- );
427
- if (!QueryExecutionId) throw new Error("Athena: failed to start query execution");
428
- while (true) {
429
- const { QueryExecution } = await client.send(
430
- new GetQueryExecutionCommand({ QueryExecutionId })
431
- );
432
- const state = QueryExecution?.Status?.State;
433
- if (state === "SUCCEEDED") break;
434
- if (state === "FAILED") {
435
- throw new Error(
436
- `Athena query failed: ${QueryExecution?.Status?.StateChangeReason ?? "unknown"}`
437
- );
438
- }
439
- if (state === "CANCELLED") throw new Error("Athena query was cancelled");
440
- await new Promise((r) => setTimeout(r, 500));
441
- }
442
- const { ResultSet } = await client.send(
443
- new GetQueryResultsCommand({ QueryExecutionId })
444
- );
445
- const resultRows = ResultSet?.Rows ?? [];
446
- if (resultRows.length === 0) return { rows: [] };
447
- const headers = resultRows[0].Data?.map((d) => d.VarCharValue ?? "") ?? [];
448
- const rows = resultRows.slice(1).map((row) => {
449
- const obj = {};
450
- row.Data?.forEach((d, i) => {
451
- obj[headers[i]] = d.VarCharValue ?? null;
452
- });
453
- return obj;
454
- });
455
- return { rows };
456
- }
457
- };
458
- }
459
-
460
- // src/connector-client/redshift.ts
461
- function createRedshiftClient(entry, connectionId) {
462
- const region = resolveEnvVar(entry, "aws-region", connectionId);
463
- const accessKeyId = resolveEnvVar(entry, "aws-access-key-id", connectionId);
464
- const secretAccessKey = resolveEnvVar(entry, "aws-secret-access-key", connectionId);
465
- const database = resolveEnvVar(entry, "database", connectionId);
466
- const clusterIdentifier = resolveEnvVarOptional(entry, "cluster-identifier");
467
- const workgroupName = resolveEnvVarOptional(entry, "workgroup-name");
468
- const secretArn = resolveEnvVarOptional(entry, "secret-arn");
469
- const dbUser = resolveEnvVarOptional(entry, "db-user");
470
- return {
471
- async query(sql) {
472
- const {
473
- RedshiftDataClient,
474
- ExecuteStatementCommand,
475
- DescribeStatementCommand,
476
- GetStatementResultCommand
477
- } = await import("@aws-sdk/client-redshift-data");
478
- const client = new RedshiftDataClient({
479
- region,
480
- credentials: { accessKeyId, secretAccessKey }
481
- });
482
- const executeParams = {
483
- Sql: sql,
484
- Database: database
485
- };
486
- if (clusterIdentifier) executeParams.ClusterIdentifier = clusterIdentifier;
487
- if (workgroupName) executeParams.WorkgroupName = workgroupName;
488
- if (secretArn) executeParams.SecretArn = secretArn;
489
- if (dbUser) executeParams.DbUser = dbUser;
490
- const { Id } = await client.send(
491
- new ExecuteStatementCommand(executeParams)
492
- );
493
- if (!Id) throw new Error("Redshift: failed to start statement execution");
494
- while (true) {
495
- const desc = await client.send(new DescribeStatementCommand({ Id }));
496
- const status = desc.Status;
497
- if (status === "FINISHED") break;
498
- if (status === "FAILED") {
499
- throw new Error(`Redshift query failed: ${desc.Error ?? "unknown"}`);
500
- }
501
- if (status === "ABORTED") throw new Error("Redshift query was aborted");
502
- await new Promise((r) => setTimeout(r, 500));
503
- }
504
- const result = await client.send(new GetStatementResultCommand({ Id }));
505
- const columns = result.ColumnMetadata?.map((c) => c.name ?? "") ?? [];
506
- const rows = (result.Records ?? []).map((record) => {
507
- const obj = {};
508
- record.forEach((field, i) => {
509
- const col = columns[i];
510
- const value = field.stringValue ?? field.longValue ?? field.doubleValue ?? field.booleanValue ?? (field.isNull ? null : field.blobValue ?? null);
511
- obj[col] = value;
512
- });
513
- return obj;
514
- });
515
- return { rows };
516
- }
517
- };
518
- }
519
-
520
- // src/connector-client/databricks.ts
521
- function createDatabricksClient(entry, connectionId) {
522
- const host = resolveEnvVar(entry, "host", connectionId);
523
- const httpPath = resolveEnvVar(entry, "http-path", connectionId);
524
- const token = resolveEnvVar(entry, "token", connectionId);
525
- return {
526
- async query(sql) {
527
- const { DBSQLClient } = await import("@databricks/sql");
528
- const client = new DBSQLClient();
529
- await client.connect({ host, path: httpPath, token });
530
- try {
531
- const session = await client.openSession();
532
- try {
533
- const operation = await session.executeStatement(sql);
534
- const result = await operation.fetchAll();
535
- await operation.close();
536
- return { rows: result };
537
- } finally {
538
- await session.close();
539
- }
540
- } finally {
541
- await client.close();
542
- }
543
- }
544
- };
545
- }
546
-
547
111
  // src/connector-client/registry.ts
548
112
  function createConnectorRegistry() {
549
- const clientCache = /* @__PURE__ */ new Map();
550
113
  function getConnectionsFilePath() {
551
114
  return process.env.CONNECTIONS_PATH ?? path.join(process.cwd(), ".squadbase/connections.json");
552
115
  }
@@ -559,59 +122,28 @@ function createConnectorRegistry() {
559
122
  return {};
560
123
  }
561
124
  }
562
- async function getClient2(connectionId) {
125
+ async function getQuery2(connectionId) {
563
126
  const connections = await loadConnections2();
564
127
  const entry = connections[connectionId];
565
128
  if (!entry) {
566
- throw new Error(`connection '${connectionId}' not found in .squadbase/connections.json`);
567
- }
568
- const connectorSlug = entry.connector.slug;
569
- const cached = clientCache.get(connectionId);
570
- if (cached) return { client: cached, connectorSlug };
571
- if (connectorSlug === "snowflake") {
572
- if (entry.connector.authType === "pat") {
573
- return { client: createSnowflakePatClient(entry, connectionId), connectorSlug };
574
- }
575
- return { client: createSnowflakeClient(entry, connectionId), connectorSlug };
576
- }
577
- if (connectorSlug === "bigquery") {
578
- if (entry.connector.authType === "oauth") {
579
- return { client: createBigQueryOAuthClient(entry, connectionId), connectorSlug };
580
- }
581
- return { client: createBigQueryClient(entry, connectionId), connectorSlug };
582
- }
583
- if (connectorSlug === "athena") {
584
- return { client: createAthenaClient(entry, connectionId), connectorSlug };
585
- }
586
- if (connectorSlug === "redshift") {
587
- return { client: createRedshiftClient(entry, connectionId), connectorSlug };
588
- }
589
- if (connectorSlug === "databricks") {
590
- return { client: createDatabricksClient(entry, connectionId), connectorSlug };
129
+ throw new Error(
130
+ `connection '${connectionId}' not found in .squadbase/connections.json`
131
+ );
591
132
  }
592
- if (connectorSlug === "mysql") {
593
- const client = createMySQLClient(entry, connectionId);
594
- clientCache.set(connectionId, client);
595
- return { client, connectorSlug };
133
+ const { slug, authType } = entry.connector;
134
+ const plugin = connectors.findByKey(slug, authType);
135
+ if (!plugin) {
136
+ throw new Error(
137
+ `connector "${slug}" (authType: ${authType ?? "none"}) is not registered in @squadbase/connectors`
138
+ );
596
139
  }
597
- if (connectorSlug === "postgresql" || connectorSlug === "squadbase-db") {
598
- const urlEnvName = entry.envVars["connection-url"];
599
- if (!urlEnvName) {
600
- throw new Error(`'connection-url' is not defined in envVars for connection '${connectionId}'`);
601
- }
602
- const connectionUrl = process.env[urlEnvName];
603
- if (!connectionUrl) {
604
- throw new Error(
605
- `environment variable '${urlEnvName}' (mapped from connection '${connectionId}') is not set`
606
- );
607
- }
608
- const client = createPostgreSQLClient(connectionUrl);
609
- clientCache.set(connectionId, client);
610
- return { client, connectorSlug };
140
+ if (!plugin.query) {
141
+ throw new Error(
142
+ `connector "${plugin.connectorKey}" does not support SQL queries. Non-SQL connectors (airtable, google-analytics, kintone, wix-store, dbt) should be used via TypeScript handlers.`
143
+ );
611
144
  }
612
- throw new Error(
613
- `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.`
614
- );
145
+ const params = resolveParams(entry, connectionId, plugin);
146
+ return (sql, namedParams) => plugin.query(params, sql, namedParams);
615
147
  }
616
148
  function reloadEnvFile2(envPath) {
617
149
  try {
@@ -634,18 +166,31 @@ function createConnectorRegistry() {
634
166
  const envPath = path.join(process.cwd(), ".env");
635
167
  try {
636
168
  fsWatch(filePath, { persistent: false }, () => {
637
- console.log("[connector-client] connections.json changed, clearing client cache");
638
- clientCache.clear();
169
+ console.log(
170
+ "[connector-client] connections.json changed"
171
+ );
639
172
  setImmediate(() => reloadEnvFile2(envPath));
640
173
  });
641
174
  } catch {
642
175
  }
643
176
  }
644
- return { getClient: getClient2, loadConnections: loadConnections2, reloadEnvFile: reloadEnvFile2, watchConnectionsFile: watchConnectionsFile2 };
177
+ return { getQuery: getQuery2, loadConnections: loadConnections2, reloadEnvFile: reloadEnvFile2, watchConnectionsFile: watchConnectionsFile2 };
178
+ }
179
+ function resolveParams(entry, connectionId, plugin) {
180
+ const params = {};
181
+ for (const param of Object.values(plugin.parameters)) {
182
+ if (param.required) {
183
+ params[param.slug] = resolveEnvVar(entry, param.slug, connectionId);
184
+ } else {
185
+ const val = resolveEnvVarOptional(entry, param.slug);
186
+ if (val !== void 0) params[param.slug] = val;
187
+ }
188
+ }
189
+ return params;
645
190
  }
646
191
 
647
192
  // src/connector-client/index.ts
648
- var { getClient, loadConnections, reloadEnvFile, watchConnectionsFile } = createConnectorRegistry();
193
+ var { getQuery, loadConnections, reloadEnvFile, watchConnectionsFile } = createConnectorRegistry();
649
194
 
650
195
  // src/cli/env-loader.ts
651
196
  import { readFileSync as readFileSync2 } from "fs";
@@ -726,24 +271,20 @@ var anyJsonDataSourceSchema = z.union([
726
271
  ]);
727
272
 
728
273
  // src/registry.ts
729
- function buildQuery(queryTemplate, parameterMeta, runtimeParams) {
274
+ function applyDefaults(parameterMeta, runtimeParams) {
730
275
  const defaults = new Map(
731
276
  parameterMeta.map((p) => [p.name, p.default ?? null])
732
277
  );
733
- const placeholderToIndex = /* @__PURE__ */ new Map();
734
- const values = [];
735
- const text = queryTemplate.replace(
736
- /\{\{(\w+)\}\}/g,
737
- (_match, name) => {
738
- if (!placeholderToIndex.has(name)) {
739
- const value = Object.prototype.hasOwnProperty.call(runtimeParams, name) ? runtimeParams[name] : defaults.get(name) ?? null;
740
- values.push(value);
741
- placeholderToIndex.set(name, values.length);
742
- }
743
- return `$${placeholderToIndex.get(name)}`;
278
+ const result = {};
279
+ for (const [key, value] of Object.entries(runtimeParams)) {
280
+ result[key] = value;
281
+ }
282
+ for (const [key, defaultVal] of defaults) {
283
+ if (!(key in result)) {
284
+ result[key] = defaultVal;
744
285
  }
745
- );
746
- return { text, values };
286
+ }
287
+ return result;
747
288
  }
748
289
  var defaultDataSourceDir = path2.join(process.cwd(), "data-source");
749
290
 
@@ -779,39 +320,16 @@ function createStubContext(params) {
779
320
  async function runSqlDataSource(slug, def, params, limit) {
780
321
  const start = Date.now();
781
322
  try {
782
- const { client, connectorSlug } = await getClient(def.connectionId);
783
- const isLiteralConnector = connectorSlug === "snowflake" || connectorSlug === "bigquery" || connectorSlug === "athena" || connectorSlug === "redshift" || connectorSlug === "databricks";
784
- let queryText;
785
- let queryValues;
786
- if (isLiteralConnector) {
787
- const defaults = new Map(
788
- (def.parameters ?? []).map((p) => [p.name, p.default ?? null])
789
- );
790
- queryText = def.query.replace(/\{\{(\w+)\}\}/g, (_match, name) => {
791
- const value = Object.prototype.hasOwnProperty.call(params, name) ? params[name] : defaults.get(name) ?? "";
792
- if (typeof value === "string") return `'${value.replace(/'/g, "''")}'`;
793
- if (value === null || value === void 0) return "NULL";
794
- return String(value);
795
- });
796
- queryValues = [];
797
- } else if (connectorSlug === "mysql") {
798
- const built = buildQuery(def.query, def.parameters ?? [], params);
799
- queryText = built.text.replace(/\$(\d+)/g, "?");
800
- queryValues = built.values;
801
- } else {
802
- const built = buildQuery(def.query, def.parameters ?? [], params);
803
- queryText = built.text;
804
- queryValues = built.values;
805
- }
806
- const result = await client.query(queryText, queryValues);
323
+ const query = await getQuery(def.connectionId);
324
+ const namedParams = applyDefaults(def.parameters ?? [], params);
325
+ const result = await query(def.query, namedParams);
807
326
  const rows = result.rows.slice(0, limit);
808
327
  return {
809
328
  slug,
810
329
  rows,
811
330
  rowCount: result.rows.length,
812
331
  durationMs: Date.now() - start,
813
- query: queryText,
814
- queryValues
332
+ query: def.query
815
333
  };
816
334
  } catch (error) {
817
335
  return {
package/dist/index.d.ts CHANGED
@@ -1,11 +1,6 @@
1
1
  import * as hono_types from 'hono/types';
2
2
  import { Hono } from 'hono';
3
3
 
4
- interface DatabaseClient {
5
- query(sql: string, params?: unknown[]): Promise<{
6
- rows: Record<string, unknown>[];
7
- }>;
8
- }
9
4
  interface ConnectionEntry {
10
5
  connector: {
11
6
  slug: string;
@@ -15,6 +10,10 @@ interface ConnectionEntry {
15
10
  }
16
11
  type ConnectionsMap = Record<string, ConnectionEntry>;
17
12
 
13
+ type QueryFn = (sql: string, namedParams?: Record<string, unknown>) => Promise<{
14
+ rows: Record<string, unknown>[];
15
+ }>;
16
+
18
17
  interface AirtableClient {
19
18
  listRecords(tableIdOrName: string, options?: {
20
19
  fields?: string[];
@@ -120,10 +119,7 @@ interface DbtClient {
120
119
  }
121
120
  declare function createDbtClient(entry: ConnectionEntry, slug: string): DbtClient;
122
121
 
123
- declare const getClient: (connectionId: string) => Promise<{
124
- client: DatabaseClient;
125
- connectorSlug: string;
126
- }>;
122
+ declare const getQuery: (connectionId: string) => Promise<QueryFn>;
127
123
  declare const loadConnections: () => Promise<ConnectionsMap>;
128
124
 
129
125
  type ConnectionFetchOptions = {
@@ -138,4 +134,4 @@ declare function connection(connectionId: string): {
138
134
 
139
135
  declare const app: Hono<hono_types.BlankEnv, hono_types.BlankSchema, "/">;
140
136
 
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 };
137
+ export { type AirtableClient, type AirtableRecord, type ConnectionEntry, type ConnectionFetchOptions, type ConnectionsMap, type DbtClient, type GoogleAnalyticsClient, type KintoneClient, type QueryFn, type WixStoreClient, connection, createAirtableClient, createDbtClient, createGoogleAnalyticsClient, createKintoneClient, createWixStoreClient, app as default, getQuery, loadConnections };