@querypanel/node-sdk 1.0.29 → 1.0.31

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.cjs CHANGED
@@ -26,14 +26,9 @@ __export(index_exports, {
26
26
  anonymizeResults: () => anonymizeResults
27
27
  });
28
28
  module.exports = __toCommonJS(index_exports);
29
- var import_node_crypto = require("crypto");
30
- var import_jose = require("jose");
31
29
 
32
30
  // src/utils/clickhouse.ts
33
31
  var WRAPPER_REGEX = /^(Nullable|LowCardinality|SimpleAggregateFunction)\((.+)\)$/i;
34
- function isNullableType(type) {
35
- return /Nullable\s*\(/i.test(type);
36
- }
37
32
  function unwrapTypeModifiers(type) {
38
33
  let current = type.trim();
39
34
  let match = WRAPPER_REGEX.exec(current);
@@ -47,26 +42,6 @@ function unwrapTypeModifiers(type) {
47
42
  }
48
43
  return current;
49
44
  }
50
- function extractPrecisionScale(type) {
51
- const unwrapped = unwrapTypeModifiers(type);
52
- const decimalMatch = unwrapped.match(/Decimal(?:\d+)?\((\d+)\s*,\s*(\d+)\)/i);
53
- if (!decimalMatch) return {};
54
- const precision = decimalMatch[1];
55
- const scale = decimalMatch[2];
56
- if (!precision || !scale) return {};
57
- return {
58
- precision: Number.parseInt(precision, 10),
59
- scale: Number.parseInt(scale, 10)
60
- };
61
- }
62
- function extractFixedStringLength(type) {
63
- const unwrapped = unwrapTypeModifiers(type);
64
- const match = unwrapped.match(/^(?:FixedString|StringFixed)\((\d+)\)$/i);
65
- if (!match) return void 0;
66
- const length = match[1];
67
- if (!length) return void 0;
68
- return Number.parseInt(length, 10);
69
- }
70
45
  function parseKeyExpression(expression) {
71
46
  if (!expression) return [];
72
47
  let value = expression.trim();
@@ -154,6 +129,10 @@ var ClickHouseAdapter = class {
154
129
  getDialect() {
155
130
  return "clickhouse";
156
131
  }
132
+ /**
133
+ * Simplified introspection: only collect table/column metadata for IngestRequest
134
+ * No indexes, constraints, or statistics
135
+ */
157
136
  async introspect(options) {
158
137
  const tablesToIntrospect = options?.tables ? normalizeTableFilter(options.tables) : this.allowedTables;
159
138
  const allowTables = tablesToIntrospect ?? [];
@@ -166,7 +145,7 @@ var ClickHouseAdapter = class {
166
145
  }
167
146
  const filterClause = hasFilter ? " AND name IN {tables:Array(String)}" : "";
168
147
  const tables = await this.query(
169
- `SELECT name, engine, comment, total_rows, total_bytes, primary_key, sorting_key
148
+ `SELECT name, engine, comment, primary_key
170
149
  FROM system.tables
171
150
  WHERE database = {db:String}${filterClause}
172
151
  ORDER BY name`,
@@ -174,7 +153,7 @@ var ClickHouseAdapter = class {
174
153
  );
175
154
  const columnFilterClause = hasFilter ? " AND table IN {tables:Array(String)}" : "";
176
155
  const columns = await this.query(
177
- `SELECT table, name, type, position, default_kind, default_expression, comment, is_in_primary_key
156
+ `SELECT table, name, type, position, comment, is_in_primary_key
178
157
  FROM system.columns
179
158
  WHERE database = {db:String}${columnFilterClause}
180
159
  ORDER BY table, position`,
@@ -189,44 +168,19 @@ var ClickHouseAdapter = class {
189
168
  const tableSchemas = tables.map((table) => {
190
169
  const tableColumns = columnsByTable.get(table.name) ?? [];
191
170
  const primaryKeyColumns = parseKeyExpression(table.primary_key);
192
- const totalRows = toNumber(table.total_rows);
193
- const totalBytes = toNumber(table.total_bytes);
194
171
  for (const column of tableColumns) {
195
172
  column.isPrimaryKey = column.isPrimaryKey || primaryKeyColumns.includes(column.name);
196
173
  }
197
- const indexes = primaryKeyColumns.length ? [
198
- {
199
- name: "primary_key",
200
- columns: primaryKeyColumns,
201
- unique: true,
202
- type: "PRIMARY KEY",
203
- ...table.primary_key ? { definition: table.primary_key } : {}
204
- }
205
- ] : [];
206
- const constraints = primaryKeyColumns.length ? [
207
- {
208
- name: "primary_key",
209
- type: "PRIMARY KEY",
210
- columns: primaryKeyColumns
211
- }
212
- ] : [];
213
174
  const base = {
214
175
  name: table.name,
215
176
  schema: this.databaseName,
216
177
  type: asTableType(table.engine),
217
- engine: table.engine,
218
- columns: tableColumns,
219
- indexes,
220
- constraints
178
+ columns: tableColumns
221
179
  };
222
180
  const comment = sanitize(table.comment);
223
181
  if (comment !== void 0) {
224
182
  base.comment = comment;
225
183
  }
226
- const statistics = buildTableStatistics(totalRows, totalBytes);
227
- if (statistics) {
228
- base.statistics = statistics;
229
- }
230
184
  return base;
231
185
  });
232
186
  return {
@@ -238,10 +192,6 @@ var ClickHouseAdapter = class {
238
192
  introspectedAt: (/* @__PURE__ */ new Date()).toISOString()
239
193
  };
240
194
  }
241
- /**
242
- * Validate that the SQL query only references allowed tables.
243
- * This is a basic validation that extracts table-like patterns from the query.
244
- */
245
195
  validateQueryTables(sql) {
246
196
  if (!this.allowedTables || this.allowedTables.length === 0) {
247
197
  return;
@@ -319,29 +269,15 @@ function normalizeTableFilter(tables) {
319
269
  return normalized;
320
270
  }
321
271
  function transformColumnRow(row) {
322
- const nullable = isNullableType(row.type);
323
272
  const unwrappedType = unwrapTypeModifiers(row.type);
324
- const { precision, scale } = extractPrecisionScale(row.type);
325
- const maxLength = extractFixedStringLength(row.type);
326
273
  const column = {
327
274
  name: row.name,
328
275
  type: unwrappedType,
329
276
  rawType: row.type,
330
- nullable,
331
- isPrimaryKey: Boolean(toNumber(row.is_in_primary_key)),
332
- isForeignKey: false
277
+ isPrimaryKey: Boolean(toNumber(row.is_in_primary_key))
333
278
  };
334
- const defaultKind = sanitize(row.default_kind);
335
- if (defaultKind !== void 0) column.defaultKind = defaultKind;
336
- const defaultExpression = sanitize(row.default_expression);
337
- if (defaultExpression !== void 0) {
338
- column.defaultExpression = defaultExpression;
339
- }
340
279
  const comment = sanitize(row.comment);
341
280
  if (comment !== void 0) column.comment = comment;
342
- if (maxLength !== void 0) column.maxLength = maxLength;
343
- if (precision !== void 0) column.precision = precision;
344
- if (scale !== void 0) column.scale = scale;
345
281
  return column;
346
282
  }
347
283
  function asTableType(engine) {
@@ -353,13 +289,6 @@ function asTableType(engine) {
353
289
  }
354
290
  return "table";
355
291
  }
356
- function buildTableStatistics(totalRows, totalBytes) {
357
- if (totalRows === void 0 && totalBytes === void 0) return void 0;
358
- const stats = {};
359
- if (totalRows !== void 0) stats.totalRows = totalRows;
360
- if (totalBytes !== void 0) stats.totalBytes = totalBytes;
361
- return stats;
362
- }
363
292
  function sanitize(value) {
364
293
  if (value === null || value === void 0) return void 0;
365
294
  const trimmed = String(value).trim();
@@ -402,10 +331,6 @@ var PostgresAdapter = class {
402
331
  const fields = result.fields.map((f) => f.name);
403
332
  return { fields, rows: result.rows };
404
333
  }
405
- /**
406
- * Validate that the SQL query only references allowed tables.
407
- * This is a basic validation that extracts table-like patterns from the query.
408
- */
409
334
  validateQueryTables(sql) {
410
335
  if (!this.allowedTables || this.allowedTables.length === 0) {
411
336
  return;
@@ -431,11 +356,6 @@ var PostgresAdapter = class {
431
356
  /**
432
357
  * Convert named params to positional array for PostgreSQL
433
358
  * PostgreSQL expects $1, $2, $3 in SQL and an array of values [val1, val2, val3]
434
- *
435
- * Supports two formats:
436
- * 1. Numeric keys: { '1': 'value1', '2': 'value2' } - maps directly to $1, $2
437
- * 2. Named keys: { 'tenant_id': 'value' } - values extracted in alphabetical order
438
- * 3. Mixed: { '1': 'value1', 'tenant_id': 'value' } - numeric keys first, then named keys
439
359
  */
440
360
  convertNamedToPositionalParams(params) {
441
361
  const numericKeys = Object.keys(params).filter((k) => /^\d+$/.test(k)).map((k) => Number.parseInt(k, 10)).sort((a, b) => a - b);
@@ -468,6 +388,10 @@ var PostgresAdapter = class {
468
388
  getDialect() {
469
389
  return "postgres";
470
390
  }
391
+ /**
392
+ * Simplified introspection: only collect table/column metadata for IngestRequest
393
+ * No indexes, constraints, or statistics
394
+ */
471
395
  async introspect(options) {
472
396
  const tablesToIntrospect = options?.tables ? normalizeTableFilter2(options.tables, this.defaultSchema) : this.allowedTables;
473
397
  const normalizedTables = tablesToIntrospect ?? [];
@@ -479,39 +403,20 @@ var PostgresAdapter = class {
479
403
  buildColumnsQuery(normalizedTables)
480
404
  );
481
405
  const columnRows = columnsResult.rows;
482
- const constraintsResult = await this.clientFn(
483
- buildConstraintsQuery(normalizedTables)
484
- );
485
- const constraintRows = constraintsResult.rows;
486
- const indexesResult = await this.clientFn(
487
- buildIndexesQuery(normalizedTables)
488
- );
489
- const indexRows = indexesResult.rows;
490
406
  const tablesByKey = /* @__PURE__ */ new Map();
491
- const columnsByKey = /* @__PURE__ */ new Map();
492
407
  for (const row of tableRows) {
493
408
  const key = tableKey(row.schema_name, row.table_name);
494
- const statistics = buildTableStatistics2(
495
- toNumber2(row.total_rows),
496
- toNumber2(row.total_bytes)
497
- );
498
409
  const table = {
499
410
  name: row.table_name,
500
411
  schema: row.schema_name,
501
412
  type: asTableType2(row.table_type),
502
- columns: [],
503
- indexes: [],
504
- constraints: []
413
+ columns: []
505
414
  };
506
415
  const comment = sanitize2(row.comment);
507
416
  if (comment !== void 0) {
508
417
  table.comment = comment;
509
418
  }
510
- if (statistics) {
511
- table.statistics = statistics;
512
- }
513
419
  tablesByKey.set(key, table);
514
- columnsByKey.set(key, /* @__PURE__ */ new Map());
515
420
  }
516
421
  for (const row of columnRows) {
517
422
  const key = tableKey(row.table_schema, row.table_name);
@@ -520,80 +425,13 @@ var PostgresAdapter = class {
520
425
  const column = {
521
426
  name: row.column_name,
522
427
  type: row.data_type,
523
- nullable: row.is_nullable.toUpperCase() === "YES",
524
- isPrimaryKey: false,
525
- isForeignKey: false
428
+ isPrimaryKey: row.is_primary_key
526
429
  };
527
430
  const rawType = row.udt_name ?? void 0;
528
431
  if (rawType !== void 0) column.rawType = rawType;
529
- const defaultExpression = sanitize2(row.column_default);
530
- if (defaultExpression !== void 0)
531
- column.defaultExpression = defaultExpression;
532
432
  const comment = sanitize2(row.description);
533
433
  if (comment !== void 0) column.comment = comment;
534
- const maxLength = row.character_maximum_length ?? void 0;
535
- if (maxLength !== void 0) column.maxLength = maxLength;
536
- const precision = row.numeric_precision ?? void 0;
537
- if (precision !== void 0) column.precision = precision;
538
- const scale = row.numeric_scale ?? void 0;
539
- if (scale !== void 0) column.scale = scale;
540
434
  table.columns.push(column);
541
- columnsByKey.get(key)?.set(row.column_name, column);
542
- }
543
- const constraintGroups = groupConstraints(constraintRows);
544
- for (const group of constraintGroups) {
545
- const key = tableKey(group.table_schema, group.table_name);
546
- const table = tablesByKey.get(key);
547
- if (!table) continue;
548
- const constraint = {
549
- name: group.constraint_name,
550
- type: group.constraint_type,
551
- columns: [...group.columns]
552
- };
553
- if (group.type === "FOREIGN KEY") {
554
- if (group.foreign_table_name) {
555
- const referencedTable = group.foreign_table_schema ? `${group.foreign_table_schema}.${group.foreign_table_name}` : group.foreign_table_name;
556
- constraint.referencedTable = referencedTable;
557
- }
558
- if (group.foreign_columns.length) {
559
- constraint.referencedColumns = [...group.foreign_columns];
560
- }
561
- }
562
- table.constraints.push(constraint);
563
- for (let index = 0; index < group.columns.length; index += 1) {
564
- const columnName = group.columns[index];
565
- if (!columnName) continue;
566
- const column = columnsByKey.get(key)?.get(columnName);
567
- if (!column) continue;
568
- if (group.type === "PRIMARY KEY") {
569
- column.isPrimaryKey = true;
570
- }
571
- if (group.type === "FOREIGN KEY") {
572
- column.isForeignKey = true;
573
- if (group.foreign_table_name) {
574
- column.foreignKeyTable = group.foreign_table_schema ? `${group.foreign_table_schema}.${group.foreign_table_name}` : group.foreign_table_name;
575
- }
576
- const referencedColumn = group.foreign_columns[index];
577
- if (referencedColumn) {
578
- column.foreignKeyColumn = referencedColumn;
579
- }
580
- }
581
- }
582
- }
583
- for (const row of indexRows) {
584
- const key = tableKey(row.schema_name, row.table_name);
585
- const table = tablesByKey.get(key);
586
- if (!table) continue;
587
- const columns = coerceStringArray(row.column_names).map((c) => c.trim()).filter(Boolean);
588
- const index = {
589
- name: row.index_name,
590
- columns,
591
- unique: Boolean(row.indisunique),
592
- type: columns.length === 1 ? "INDEX" : "COMPOSITE INDEX"
593
- };
594
- const definition = sanitize2(row.definition);
595
- if (definition !== void 0) index.definition = definition;
596
- table.indexes.push(index);
597
435
  }
598
436
  const tables = Array.from(tablesByKey.values()).sort((a, b) => {
599
437
  if (a.schema === b.schema) {
@@ -611,36 +449,6 @@ var PostgresAdapter = class {
611
449
  };
612
450
  }
613
451
  };
614
- function groupConstraints(rows) {
615
- const groups = /* @__PURE__ */ new Map();
616
- for (const row of rows) {
617
- const key = `${row.table_schema}.${row.table_name}.${row.constraint_name}`;
618
- let group = groups.get(key);
619
- if (!group) {
620
- group = {
621
- table_schema: row.table_schema,
622
- table_name: row.table_name,
623
- constraint_name: row.constraint_name,
624
- constraint_type: row.constraint_type,
625
- columns: [],
626
- foreign_columns: [],
627
- type: row.constraint_type
628
- };
629
- groups.set(key, group);
630
- }
631
- if (row.column_name) {
632
- group.columns.push(row.column_name);
633
- }
634
- if (row.constraint_type === "FOREIGN KEY") {
635
- group.foreign_table_schema = row.foreign_table_schema;
636
- group.foreign_table_name = row.foreign_table_name;
637
- if (row.foreign_column_name) {
638
- group.foreign_columns.push(row.foreign_column_name);
639
- }
640
- }
641
- }
642
- return Array.from(groups.values());
643
- }
644
452
  function normalizeTableFilter2(tables, defaultSchema) {
645
453
  if (!tables?.length) return [];
646
454
  const normalized = [];
@@ -673,9 +481,7 @@ function buildTablesQuery(tables) {
673
481
  WHEN 'm' THEN 'materialized_view'
674
482
  ELSE c.relkind::text
675
483
  END AS table_type,
676
- obj_description(c.oid) AS comment,
677
- c.reltuples AS total_rows,
678
- pg_total_relation_size(c.oid) AS total_bytes
484
+ obj_description(c.oid) AS comment
679
485
  FROM pg_class c
680
486
  JOIN pg_namespace n ON n.oid = c.relnamespace
681
487
  WHERE n.nspname NOT IN ('pg_catalog', 'information_schema')
@@ -695,13 +501,18 @@ function buildColumnsQuery(tables) {
695
501
  cols.column_name,
696
502
  cols.data_type,
697
503
  cols.udt_name,
698
- cols.is_nullable,
699
- cols.column_default,
700
- cols.character_maximum_length,
701
- cols.numeric_precision,
702
- cols.numeric_scale,
703
- cols.ordinal_position,
704
- pgd.description
504
+ pgd.description,
505
+ EXISTS(
506
+ SELECT 1
507
+ FROM information_schema.table_constraints tc
508
+ JOIN information_schema.key_column_usage kcu
509
+ ON tc.constraint_name = kcu.constraint_name
510
+ AND tc.table_schema = kcu.table_schema
511
+ WHERE tc.constraint_type = 'PRIMARY KEY'
512
+ AND tc.table_schema = cols.table_schema
513
+ AND tc.table_name = cols.table_name
514
+ AND kcu.column_name = cols.column_name
515
+ ) AS is_primary_key
705
516
  FROM information_schema.columns cols
706
517
  LEFT JOIN pg_catalog.pg_class c
707
518
  ON c.relname = cols.table_name
@@ -716,50 +527,6 @@ function buildColumnsQuery(tables) {
716
527
  ${filter}
717
528
  ORDER BY cols.table_schema, cols.table_name, cols.ordinal_position;`;
718
529
  }
719
- function buildConstraintsQuery(tables) {
720
- const filter = buildFilterClause(tables, "tc.table_schema", "tc.table_name");
721
- return `SELECT
722
- tc.table_schema,
723
- tc.table_name,
724
- tc.constraint_name,
725
- tc.constraint_type,
726
- kcu.column_name,
727
- ccu.table_schema AS foreign_table_schema,
728
- ccu.table_name AS foreign_table_name,
729
- ccu.column_name AS foreign_column_name
730
- FROM information_schema.table_constraints tc
731
- LEFT JOIN information_schema.key_column_usage kcu
732
- ON tc.constraint_name = kcu.constraint_name
733
- AND tc.table_schema = kcu.table_schema
734
- LEFT JOIN information_schema.constraint_column_usage ccu
735
- ON ccu.constraint_name = tc.constraint_name
736
- AND ccu.table_schema = tc.table_schema
737
- WHERE tc.constraint_type IN ('PRIMARY KEY', 'UNIQUE', 'FOREIGN KEY')
738
- AND tc.table_schema NOT IN ('pg_catalog', 'information_schema')
739
- ${filter}
740
- ORDER BY tc.table_schema, tc.table_name, tc.constraint_name, kcu.ordinal_position;`;
741
- }
742
- function buildIndexesQuery(tables) {
743
- const filter = buildFilterClause(tables, "n.nspname", "c.relname");
744
- return `SELECT
745
- n.nspname AS schema_name,
746
- c.relname AS table_name,
747
- ci.relname AS index_name,
748
- idx.indisunique,
749
- array_remove(
750
- array_agg(pg_get_indexdef(idx.indexrelid, g.k, true) ORDER BY g.k),
751
- NULL
752
- ) AS column_names,
753
- pg_get_indexdef(idx.indexrelid) AS definition
754
- FROM pg_class c
755
- JOIN pg_namespace n ON n.oid = c.relnamespace
756
- JOIN pg_index idx ON idx.indrelid = c.oid
757
- JOIN pg_class ci ON ci.oid = idx.indexrelid
758
- JOIN LATERAL generate_subscripts(idx.indkey, 1) AS g(k) ON true
759
- WHERE n.nspname NOT IN ('pg_catalog', 'information_schema')
760
- ${filter}
761
- GROUP BY n.nspname, c.relname, ci.relname, idx.indisunique, idx.indexrelid;`;
762
- }
763
530
  function buildFilterClause(tables, schemaExpr, tableExpr) {
764
531
  if (!tables.length) return "";
765
532
  const clauses = tables.map(({ schema, table }) => {
@@ -780,50 +547,21 @@ function asTableType2(value) {
780
547
  }
781
548
  return "table";
782
549
  }
783
- function buildTableStatistics2(totalRows, totalBytes) {
784
- if (totalRows === void 0 && totalBytes === void 0) return void 0;
785
- const stats = {};
786
- if (totalRows !== void 0) stats.totalRows = totalRows;
787
- if (totalBytes !== void 0) stats.totalBytes = totalBytes;
788
- return stats;
789
- }
790
550
  function sanitize2(value) {
791
551
  if (value === null || value === void 0) return void 0;
792
552
  const trimmed = String(value).trim();
793
553
  return trimmed.length ? trimmed : void 0;
794
554
  }
795
- function toNumber2(value) {
796
- if (value === null || value === void 0) return void 0;
797
- if (typeof value === "number") return value;
798
- const parsed = Number.parseFloat(String(value));
799
- return Number.isNaN(parsed) ? void 0 : parsed;
800
- }
801
- function coerceStringArray(value) {
802
- if (!value) return [];
803
- if (Array.isArray(value)) {
804
- return value.map((entry) => String(entry));
805
- }
806
- const text = String(value).trim();
807
- if (!text) return [];
808
- const withoutBraces = text.startsWith("{") && text.endsWith("}") ? text.slice(1, -1) : text;
809
- if (!withoutBraces) return [];
810
- return withoutBraces.split(",").map((part) => part.trim().replace(/^"(.+)"$/, "$1")).filter(Boolean);
811
- }
812
555
 
813
- // src/index.ts
814
- var QueryPanelSdkAPI = class {
556
+ // src/core/client.ts
557
+ var import_node_crypto = require("crypto");
558
+ var ApiClient = class {
815
559
  baseUrl;
816
560
  privateKey;
817
561
  organizationId;
818
562
  defaultTenantId;
819
563
  additionalHeaders;
820
564
  fetchImpl;
821
- cachedPrivateKey;
822
- databases = /* @__PURE__ */ new Map();
823
- databaseMetadata = /* @__PURE__ */ new Map();
824
- defaultDatabase;
825
- lastSyncHashes = /* @__PURE__ */ new Map();
826
- syncedDatabases = /* @__PURE__ */ new Set();
827
565
  constructor(baseUrl, privateKey, organizationId, options) {
828
566
  if (!baseUrl) {
829
567
  throw new Error("Base URL is required");
@@ -846,561 +584,188 @@ var QueryPanelSdkAPI = class {
846
584
  );
847
585
  }
848
586
  }
849
- attachClickhouse(name, clientFn, options) {
850
- const adapter = new ClickHouseAdapter(clientFn, options);
851
- this.attachDatabase(name, adapter);
852
- const metadata = {
853
- name,
854
- dialect: "clickhouse",
855
- description: options?.description,
856
- tags: options?.tags,
857
- tenantFieldName: options?.tenantFieldName,
858
- tenantFieldType: options?.tenantFieldType ?? "String",
859
- enforceTenantIsolation: options?.tenantFieldName ? options?.enforceTenantIsolation ?? true : void 0
587
+ getDefaultTenantId() {
588
+ return this.defaultTenantId;
589
+ }
590
+ async get(path, tenantId, userId, scopes, signal, sessionId) {
591
+ return await this.request(path, {
592
+ method: "GET",
593
+ headers: await this.buildHeaders(
594
+ tenantId,
595
+ userId,
596
+ scopes,
597
+ false,
598
+ sessionId
599
+ ),
600
+ signal
601
+ });
602
+ }
603
+ async post(path, body, tenantId, userId, scopes, signal, sessionId) {
604
+ return await this.request(path, {
605
+ method: "POST",
606
+ headers: await this.buildHeaders(
607
+ tenantId,
608
+ userId,
609
+ scopes,
610
+ true,
611
+ sessionId
612
+ ),
613
+ body: JSON.stringify(body ?? {}),
614
+ signal
615
+ });
616
+ }
617
+ async put(path, body, tenantId, userId, scopes, signal, sessionId) {
618
+ return await this.request(path, {
619
+ method: "PUT",
620
+ headers: await this.buildHeaders(
621
+ tenantId,
622
+ userId,
623
+ scopes,
624
+ true,
625
+ sessionId
626
+ ),
627
+ body: JSON.stringify(body ?? {}),
628
+ signal
629
+ });
630
+ }
631
+ async delete(path, tenantId, userId, scopes, signal, sessionId) {
632
+ return await this.request(path, {
633
+ method: "DELETE",
634
+ headers: await this.buildHeaders(
635
+ tenantId,
636
+ userId,
637
+ scopes,
638
+ false,
639
+ sessionId
640
+ ),
641
+ signal
642
+ });
643
+ }
644
+ async request(path, init) {
645
+ const response = await this.fetchImpl(`${this.baseUrl}${path}`, init);
646
+ const text = await response.text();
647
+ let json;
648
+ try {
649
+ json = text ? JSON.parse(text) : void 0;
650
+ } catch {
651
+ json = void 0;
652
+ }
653
+ if (!response.ok) {
654
+ const error = new Error(
655
+ json?.error || response.statusText || "Request failed"
656
+ );
657
+ error.status = response.status;
658
+ if (json?.details) error.details = json.details;
659
+ throw error;
660
+ }
661
+ return json;
662
+ }
663
+ async buildHeaders(tenantId, userId, scopes, includeJson = true, sessionId) {
664
+ const token = await this.generateJWT(tenantId, userId, scopes);
665
+ const headers = {
666
+ Authorization: `Bearer ${token}`,
667
+ Accept: "application/json"
860
668
  };
861
- this.databaseMetadata.set(name, metadata);
669
+ if (includeJson) {
670
+ headers["Content-Type"] = "application/json";
671
+ }
672
+ if (sessionId) {
673
+ headers["x-session-id"] = sessionId;
674
+ }
675
+ if (this.additionalHeaders) {
676
+ Object.assign(headers, this.additionalHeaders);
677
+ }
678
+ return headers;
862
679
  }
863
- attachPostgres(name, clientFn, options) {
864
- const adapter = new PostgresAdapter(clientFn, options);
865
- this.attachDatabase(name, adapter);
866
- const metadata = {
867
- name,
868
- dialect: "postgres",
869
- description: options?.description,
870
- tags: options?.tags,
871
- tenantFieldName: options?.tenantFieldName,
872
- enforceTenantIsolation: options?.tenantFieldName ? options?.enforceTenantIsolation ?? true : void 0
680
+ async generateJWT(tenantId, userId, scopes) {
681
+ const header = {
682
+ alg: "RS256",
683
+ typ: "JWT"
873
684
  };
874
- this.databaseMetadata.set(name, metadata);
685
+ const payload = {
686
+ organizationId: this.organizationId,
687
+ tenantId
688
+ };
689
+ if (userId) payload.userId = userId;
690
+ if (scopes?.length) payload.scopes = scopes;
691
+ const encodeJson = (obj) => {
692
+ const json = JSON.stringify(obj);
693
+ const base64 = Buffer.from(json).toString("base64");
694
+ return base64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, "");
695
+ };
696
+ const encodedHeader = encodeJson(header);
697
+ const encodedPayload = encodeJson(payload);
698
+ const data = `${encodedHeader}.${encodedPayload}`;
699
+ const signer = (0, import_node_crypto.createSign)("RSA-SHA256");
700
+ signer.update(data);
701
+ signer.end();
702
+ const signature = signer.sign(this.privateKey);
703
+ const encodedSignature = signature.toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, "");
704
+ return `${data}.${encodedSignature}`;
875
705
  }
876
- attachDatabase(name, adapter) {
706
+ };
707
+
708
+ // src/core/query-engine.ts
709
+ var QueryEngine = class {
710
+ databases = /* @__PURE__ */ new Map();
711
+ databaseMetadata = /* @__PURE__ */ new Map();
712
+ defaultDatabase;
713
+ attachDatabase(name, adapter, metadata) {
877
714
  this.databases.set(name, adapter);
715
+ this.databaseMetadata.set(name, metadata);
878
716
  if (!this.defaultDatabase) {
879
717
  this.defaultDatabase = name;
880
718
  }
881
719
  }
882
- async syncSchema(databaseName, options, signal) {
883
- const tenantId = this.resolveTenantId(options.tenantId);
884
- const adapter = this.getDatabase(databaseName);
885
- const introspection = await adapter.introspect(
886
- options.tables ? { tables: options.tables } : void 0
887
- );
888
- const payload = this.buildSchemaRequest(
889
- databaseName,
890
- adapter,
891
- introspection
892
- );
893
- const hash = this.hashSchemaRequest(payload);
894
- const previousHash = this.lastSyncHashes.get(databaseName);
895
- if (!options.force && previousHash === hash) {
896
- return {
897
- success: true,
898
- message: "Schema unchanged; skipping ingestion",
899
- chunks: 0,
900
- chunks_with_annotations: 0,
901
- schema_hash: hash,
902
- skipped: true
903
- };
720
+ getDatabase(name) {
721
+ const dbName = name ?? this.defaultDatabase;
722
+ if (!dbName) {
723
+ throw new Error("No database attached.");
904
724
  }
905
- const sessionId = (0, import_node_crypto.randomUUID)();
906
- const response = await this.post(
907
- "/ingest",
908
- payload,
909
- tenantId,
910
- options.userId,
911
- options.scopes,
912
- signal,
913
- sessionId
914
- );
915
- this.lastSyncHashes.set(databaseName, hash);
916
- this.syncedDatabases.add(databaseName);
917
- return response;
918
- }
919
- async ingestKnowledgeBaseChunks(payload, options, signal) {
920
- const tenantId = this.resolveTenantId(
921
- payload.tenantId ?? options?.tenantId
922
- );
923
- return await this.post(
924
- "/knowledge-base/chunks",
925
- {
926
- organization_id: this.organizationId,
927
- tenant_id: tenantId,
928
- database: payload.database,
929
- dialect: payload.dialect,
930
- tables: payload.tables
931
- },
932
- tenantId,
933
- options?.userId,
934
- options?.scopes,
935
- signal
936
- );
937
- }
938
- async introspect(databaseName, tables) {
939
- const adapter = this.getDatabase(databaseName);
940
- return await adapter.introspect(tables ? { tables } : void 0);
941
- }
942
- async ask(question, options, signal) {
943
- const tenantId = this.resolveTenantId(options.tenantId);
944
- await this.ensureSchemasSynced(
945
- tenantId,
946
- options.userId,
947
- options.scopes,
948
- options.disableAutoSync
949
- );
950
- const sessionId = (0, import_node_crypto.randomUUID)();
951
- const queryResponse = await this.post(
952
- "/query",
953
- {
954
- question,
955
- ...options.lastError ? { last_error: options.lastError } : {},
956
- ...options.previousSql ? { previous_sql: options.previousSql } : {},
957
- ...options.maxRetry ? { max_retry: options.maxRetry } : {}
958
- },
959
- tenantId,
960
- options.userId,
961
- options.scopes,
962
- signal,
963
- sessionId
964
- );
965
- const databaseName = options.database ?? this.defaultDatabase;
966
- if (!databaseName) {
725
+ const adapter = this.databases.get(dbName);
726
+ if (!adapter) {
967
727
  throw new Error(
968
- "No database attached. Call attachPostgres/attachClickhouse first."
728
+ `Database '${dbName}' not found. Attached: ${Array.from(
729
+ this.databases.keys()
730
+ ).join(", ")}`
969
731
  );
970
732
  }
733
+ return adapter;
734
+ }
735
+ getDatabaseMetadata(name) {
736
+ const dbName = name ?? this.defaultDatabase;
737
+ if (!dbName) return void 0;
738
+ return this.databaseMetadata.get(dbName);
739
+ }
740
+ getDefaultDatabase() {
741
+ return this.defaultDatabase;
742
+ }
743
+ async validateAndExecute(sql, params, databaseName, tenantId) {
971
744
  const adapter = this.getDatabase(databaseName);
972
- const paramMetadata = Array.isArray(queryResponse.params) ? queryResponse.params : [];
973
- const paramValues = this.mapGeneratedParams(paramMetadata);
974
- const metadata = this.databaseMetadata.get(databaseName);
745
+ const metadata = this.getDatabaseMetadata(databaseName);
746
+ let finalSql = sql;
975
747
  if (metadata) {
976
- queryResponse.sql = this.ensureTenantIsolation(
977
- queryResponse.sql,
978
- paramValues,
979
- metadata,
980
- tenantId
981
- );
748
+ finalSql = this.ensureTenantIsolation(sql, params, metadata, tenantId);
982
749
  }
983
- await adapter.validate(queryResponse.sql, paramValues);
984
- const execution = await adapter.execute(queryResponse.sql, paramValues);
985
- const rows = execution.rows ?? [];
986
- let chart = {
987
- vegaLiteSpec: null,
988
- notes: rows.length === 0 ? "Query returned no rows." : null
750
+ await adapter.validate(finalSql, params);
751
+ const result = await adapter.execute(finalSql, params);
752
+ return {
753
+ rows: result.rows,
754
+ fields: result.fields
989
755
  };
990
- if (rows.length > 0) {
991
- const chartResponse = await this.post(
992
- "/chart",
993
- {
994
- question,
995
- sql: queryResponse.sql,
996
- rationale: queryResponse.rationale,
997
- fields: execution.fields,
998
- rows: anonymizeResults(rows),
999
- max_retries: options.chartMaxRetries ?? 3,
1000
- query_id: queryResponse.queryId
1001
- },
1002
- tenantId,
1003
- options.userId,
1004
- options.scopes,
1005
- signal,
1006
- sessionId
756
+ }
757
+ async execute(sql, params, databaseName) {
758
+ try {
759
+ const adapter = this.getDatabase(databaseName);
760
+ const result = await adapter.execute(sql, params);
761
+ return result.rows;
762
+ } catch (error) {
763
+ console.warn(
764
+ `Failed to execute SQL locally for database '${databaseName}':`,
765
+ error
1007
766
  );
1008
- chart = {
1009
- vegaLiteSpec: chartResponse.chart ? {
1010
- ...chartResponse.chart,
1011
- data: { values: rows }
1012
- } : null,
1013
- notes: chartResponse.notes
1014
- };
767
+ return [];
1015
768
  }
1016
- return {
1017
- sql: queryResponse.sql,
1018
- params: paramValues,
1019
- paramMetadata,
1020
- rationale: queryResponse.rationale,
1021
- dialect: queryResponse.dialect,
1022
- queryId: queryResponse.queryId,
1023
- rows,
1024
- fields: execution.fields,
1025
- chart,
1026
- context: queryResponse.context
1027
- };
1028
- }
1029
- async upsertAnnotation(input, options, signal) {
1030
- const tenantId = this.resolveTenantId(input.tenantId ?? options?.tenantId);
1031
- const response = await this.post(
1032
- "/knowledge-base/annotations",
1033
- {
1034
- organization_id: this.organizationId,
1035
- tenant_id: tenantId,
1036
- target_identifier: input.targetIdentifier,
1037
- content: input.content,
1038
- user_id: input.userId
1039
- },
1040
- tenantId,
1041
- options?.userId,
1042
- options?.scopes,
1043
- signal
1044
- );
1045
- return response.annotation;
1046
- }
1047
- async listAnnotations(options, signal) {
1048
- const tenantId = this.resolveTenantId(options?.tenantId);
1049
- const response = await this.get(
1050
- "/knowledge-base/annotations",
1051
- tenantId,
1052
- options?.userId,
1053
- options?.scopes,
1054
- signal
1055
- );
1056
- return response.annotations;
1057
- }
1058
- async getAnnotation(targetIdentifier, options, signal) {
1059
- const tenantId = this.resolveTenantId(options?.tenantId);
1060
- const response = await this.get(
1061
- `/knowledge-base/annotations/${encodeURIComponent(targetIdentifier)}`,
1062
- tenantId,
1063
- options?.userId,
1064
- options?.scopes,
1065
- signal
1066
- ).catch((error) => {
1067
- if (error?.status === 404) {
1068
- return { success: false, annotation: null };
1069
- }
1070
- throw error;
1071
- });
1072
- return response.annotation ?? null;
1073
- }
1074
- async deleteAnnotation(targetIdentifier, options, signal) {
1075
- const tenantId = this.resolveTenantId(options?.tenantId);
1076
- await this.delete(
1077
- `/knowledge-base/annotations/${encodeURIComponent(targetIdentifier)}`,
1078
- tenantId,
1079
- options?.userId,
1080
- options?.scopes,
1081
- signal
1082
- );
1083
- }
1084
- async createChart(body, options, signal) {
1085
- const tenantId = this.resolveTenantId(options?.tenantId);
1086
- return await this.post(
1087
- "/charts",
1088
- body,
1089
- tenantId,
1090
- options?.userId,
1091
- options?.scopes,
1092
- signal
1093
- );
1094
- }
1095
- async listCharts(options, signal) {
1096
- const tenantId = this.resolveTenantId(options?.tenantId);
1097
- const params = new URLSearchParams();
1098
- if (options?.pagination?.page)
1099
- params.set("page", `${options.pagination.page}`);
1100
- if (options?.pagination?.limit)
1101
- params.set("limit", `${options.pagination.limit}`);
1102
- if (options?.sortBy) params.set("sort_by", options.sortBy);
1103
- if (options?.sortDir) params.set("sort_dir", options.sortDir);
1104
- if (options?.title) params.set("title", options.title);
1105
- if (options?.userFilter) params.set("user_id", options.userFilter);
1106
- if (options?.createdFrom) params.set("created_from", options.createdFrom);
1107
- if (options?.createdTo) params.set("created_to", options.createdTo);
1108
- if (options?.updatedFrom) params.set("updated_from", options.updatedFrom);
1109
- if (options?.updatedTo) params.set("updated_to", options.updatedTo);
1110
- const response = await this.get(
1111
- `/charts${params.toString() ? `?${params.toString()}` : ""}`,
1112
- tenantId,
1113
- options?.userId,
1114
- options?.scopes,
1115
- signal
1116
- );
1117
- if (options?.includeData) {
1118
- response.data = await Promise.all(
1119
- response.data.map(async (chart) => ({
1120
- ...chart,
1121
- vega_lite_spec: {
1122
- ...chart.vega_lite_spec,
1123
- data: {
1124
- values: await this.runSafeQueryOnClient(
1125
- chart.sql,
1126
- chart.database ?? void 0,
1127
- chart.sql_params ?? void 0
1128
- )
1129
- }
1130
- }
1131
- }))
1132
- );
1133
- }
1134
- return response;
1135
- }
1136
- async getChart(id, options, signal) {
1137
- const tenantId = this.resolveTenantId(options?.tenantId);
1138
- const chart = await this.get(
1139
- `/charts/${encodeURIComponent(id)}`,
1140
- tenantId,
1141
- options?.userId,
1142
- options?.scopes,
1143
- signal
1144
- );
1145
- return {
1146
- ...chart,
1147
- vega_lite_spec: {
1148
- ...chart.vega_lite_spec,
1149
- data: {
1150
- values: await this.runSafeQueryOnClient(
1151
- chart.sql,
1152
- chart.database ?? void 0,
1153
- chart.sql_params ?? void 0
1154
- )
1155
- }
1156
- }
1157
- };
1158
- }
1159
- async updateChart(id, body, options, signal) {
1160
- const tenantId = this.resolveTenantId(options?.tenantId);
1161
- return await this.put(
1162
- `/charts/${encodeURIComponent(id)}`,
1163
- body,
1164
- tenantId,
1165
- options?.userId,
1166
- options?.scopes,
1167
- signal
1168
- );
1169
- }
1170
- async deleteChart(id, options, signal) {
1171
- const tenantId = this.resolveTenantId(options?.tenantId);
1172
- await this.delete(
1173
- `/charts/${encodeURIComponent(id)}`,
1174
- tenantId,
1175
- options?.userId,
1176
- options?.scopes,
1177
- signal
1178
- );
1179
- }
1180
- async createActiveChart(body, options, signal) {
1181
- const tenantId = this.resolveTenantId(options?.tenantId);
1182
- return await this.post(
1183
- "/active-charts",
1184
- body,
1185
- tenantId,
1186
- options?.userId,
1187
- options?.scopes,
1188
- signal
1189
- );
1190
- }
1191
- async listActiveCharts(options, signal) {
1192
- const tenantId = this.resolveTenantId(options?.tenantId);
1193
- const params = new URLSearchParams();
1194
- if (options?.pagination?.page)
1195
- params.set("page", `${options.pagination.page}`);
1196
- if (options?.pagination?.limit)
1197
- params.set("limit", `${options.pagination.limit}`);
1198
- if (options?.sortBy) params.set("sort_by", options.sortBy);
1199
- if (options?.sortDir) params.set("sort_dir", options.sortDir);
1200
- if (options?.title) params.set("name", options.title);
1201
- if (options?.userFilter) params.set("user_id", options.userFilter);
1202
- if (options?.createdFrom) params.set("created_from", options.createdFrom);
1203
- if (options?.createdTo) params.set("created_to", options.createdTo);
1204
- if (options?.updatedFrom) params.set("updated_from", options.updatedFrom);
1205
- if (options?.updatedTo) params.set("updated_to", options.updatedTo);
1206
- const response = await this.get(
1207
- `/active-charts${params.toString() ? `?${params.toString()}` : ""}`,
1208
- tenantId,
1209
- options?.userId,
1210
- options?.scopes,
1211
- signal
1212
- );
1213
- if (options?.withData) {
1214
- response.data = await Promise.all(
1215
- response.data.map(async (active) => ({
1216
- ...active,
1217
- chart: active.chart ? await this.getChart(active.chart_id, options, signal) : null
1218
- }))
1219
- );
1220
- }
1221
- return response;
1222
- }
1223
- async getActiveChart(id, options, signal) {
1224
- const tenantId = this.resolveTenantId(options?.tenantId);
1225
- const active = await this.get(
1226
- `/active-charts/${encodeURIComponent(id)}`,
1227
- tenantId,
1228
- options?.userId,
1229
- options?.scopes,
1230
- signal
1231
- );
1232
- if (options?.withData && active.chart_id) {
1233
- return {
1234
- ...active,
1235
- chart: await this.getChart(active.chart_id, options, signal)
1236
- };
1237
- }
1238
- return active;
1239
- }
1240
- async updateActiveChart(id, body, options, signal) {
1241
- const tenantId = this.resolveTenantId(options?.tenantId);
1242
- return await this.put(
1243
- `/active-charts/${encodeURIComponent(id)}`,
1244
- body,
1245
- tenantId,
1246
- options?.userId,
1247
- options?.scopes,
1248
- signal
1249
- );
1250
- }
1251
- async deleteActiveChart(id, options, signal) {
1252
- const tenantId = this.resolveTenantId(options?.tenantId);
1253
- await this.delete(
1254
- `/active-charts/${encodeURIComponent(id)}`,
1255
- tenantId,
1256
- options?.userId,
1257
- options?.scopes,
1258
- signal
1259
- );
1260
- }
1261
- getDatabase(name) {
1262
- const dbName = name ?? this.defaultDatabase;
1263
- if (!dbName) {
1264
- throw new Error("No database attached.");
1265
- }
1266
- const adapter = this.databases.get(dbName);
1267
- if (!adapter) {
1268
- throw new Error(
1269
- `Database '${dbName}' not found. Attached: ${Array.from(
1270
- this.databases.keys()
1271
- ).join(", ")}`
1272
- );
1273
- }
1274
- return adapter;
1275
- }
1276
- async ensureSchemasSynced(tenantId, userId, scopes, disableAutoSync) {
1277
- if (disableAutoSync) return;
1278
- const unsynced = Array.from(this.databases.keys()).filter(
1279
- (name) => !this.syncedDatabases.has(name)
1280
- );
1281
- await Promise.all(
1282
- unsynced.map(
1283
- (name) => this.syncSchema(name, { tenantId, userId, scopes }).catch((error) => {
1284
- console.warn(`Failed to sync schema for ${name}:`, error);
1285
- })
1286
- )
1287
- );
1288
- }
1289
- resolveTenantId(tenantId) {
1290
- const resolved = tenantId ?? this.defaultTenantId;
1291
- if (!resolved) {
1292
- throw new Error(
1293
- "tenantId is required. Provide it per request or via defaultTenantId option."
1294
- );
1295
- }
1296
- return resolved;
1297
- }
1298
- async headers(tenantId, userId, scopes, includeJson = true, sessionId) {
1299
- const token = await this.generateJWT(tenantId, userId, scopes);
1300
- const headers = {
1301
- Authorization: `Bearer ${token}`,
1302
- Accept: "application/json"
1303
- };
1304
- if (includeJson) {
1305
- headers["Content-Type"] = "application/json";
1306
- }
1307
- if (sessionId) {
1308
- headers["x-session-id"] = sessionId;
1309
- }
1310
- if (this.additionalHeaders) {
1311
- Object.assign(headers, this.additionalHeaders);
1312
- }
1313
- return headers;
1314
- }
1315
- async request(path, init) {
1316
- const response = await this.fetchImpl(`${this.baseUrl}${path}`, init);
1317
- const text = await response.text();
1318
- let json;
1319
- try {
1320
- json = text ? JSON.parse(text) : void 0;
1321
- } catch {
1322
- json = void 0;
1323
- }
1324
- if (!response.ok) {
1325
- const error = new Error(
1326
- json?.error || response.statusText || "Request failed"
1327
- );
1328
- error.status = response.status;
1329
- if (json?.details) error.details = json.details;
1330
- throw error;
1331
- }
1332
- return json;
1333
- }
1334
- async get(path, tenantId, userId, scopes, signal, sessionId) {
1335
- return await this.request(path, {
1336
- method: "GET",
1337
- headers: await this.headers(tenantId, userId, scopes, false, sessionId),
1338
- signal
1339
- });
1340
- }
1341
- async post(path, body, tenantId, userId, scopes, signal, sessionId) {
1342
- return await this.request(path, {
1343
- method: "POST",
1344
- headers: await this.headers(tenantId, userId, scopes, true, sessionId),
1345
- body: JSON.stringify(body ?? {}),
1346
- signal
1347
- });
1348
- }
1349
- async put(path, body, tenantId, userId, scopes, signal, sessionId) {
1350
- return await this.request(path, {
1351
- method: "PUT",
1352
- headers: await this.headers(tenantId, userId, scopes, true, sessionId),
1353
- body: JSON.stringify(body ?? {}),
1354
- signal
1355
- });
1356
- }
1357
- async delete(path, tenantId, userId, scopes, signal, sessionId) {
1358
- return await this.request(path, {
1359
- method: "DELETE",
1360
- headers: await this.headers(tenantId, userId, scopes, false, sessionId),
1361
- signal
1362
- });
1363
- }
1364
- async generateJWT(tenantId, userId, scopes) {
1365
- if (!this.cachedPrivateKey) {
1366
- this.cachedPrivateKey = await (0, import_jose.importPKCS8)(this.privateKey, "RS256");
1367
- }
1368
- const payload = {
1369
- organizationId: this.organizationId,
1370
- tenantId
1371
- };
1372
- if (userId) payload.userId = userId;
1373
- if (scopes?.length) payload.scopes = scopes;
1374
- return await new import_jose.SignJWT(payload).setProtectedHeader({ alg: "RS256" }).setIssuedAt().setExpirationTime("1h").sign(this.cachedPrivateKey);
1375
- }
1376
- buildSchemaRequest(databaseName, adapter, introspection) {
1377
- const dialect = adapter.getDialect();
1378
- const tables = introspection.tables.map((table) => ({
1379
- table_name: table.name,
1380
- description: table.comment ?? `Table ${table.name}`,
1381
- columns: table.columns.map((column) => ({
1382
- name: column.name,
1383
- data_type: column.rawType ?? column.type,
1384
- is_primary_key: Boolean(column.isPrimaryKey),
1385
- description: column.comment ?? ""
1386
- }))
1387
- }));
1388
- return {
1389
- database: databaseName,
1390
- dialect,
1391
- tables
1392
- };
1393
- }
1394
- hashSchemaRequest(payload) {
1395
- const normalized = payload.tables.map((table) => ({
1396
- name: table.table_name,
1397
- columns: table.columns.map((column) => ({
1398
- name: column.name,
1399
- type: column.data_type,
1400
- primary: column.is_primary_key
1401
- }))
1402
- }));
1403
- return (0, import_node_crypto.createHash)("sha256").update(JSON.stringify(normalized)).digest("hex");
1404
769
  }
1405
770
  mapGeneratedParams(params) {
1406
771
  const record = {};
@@ -1435,20 +800,375 @@ var QueryPanelSdkAPI = class {
1435
800
  }
1436
801
  return `${sql} WHERE ${tenantPredicate}`;
1437
802
  }
1438
- async runSafeQueryOnClient(sql, database, params) {
803
+ };
804
+
805
+ // src/routes/charts.ts
806
+ async function createChart(client, body, options, signal) {
807
+ const tenantId = resolveTenantId(client, options?.tenantId);
808
+ return await client.post(
809
+ "/charts",
810
+ body,
811
+ tenantId,
812
+ options?.userId,
813
+ options?.scopes,
814
+ signal
815
+ );
816
+ }
817
+ async function listCharts(client, queryEngine, options, signal) {
818
+ const tenantId = resolveTenantId(client, options?.tenantId);
819
+ const params = new URLSearchParams();
820
+ if (options?.pagination?.page)
821
+ params.set("page", `${options.pagination.page}`);
822
+ if (options?.pagination?.limit)
823
+ params.set("limit", `${options.pagination.limit}`);
824
+ if (options?.sortBy) params.set("sort_by", options.sortBy);
825
+ if (options?.sortDir) params.set("sort_dir", options.sortDir);
826
+ if (options?.title) params.set("title", options.title);
827
+ if (options?.userFilter) params.set("user_id", options.userFilter);
828
+ if (options?.createdFrom) params.set("created_from", options.createdFrom);
829
+ if (options?.createdTo) params.set("created_to", options.createdTo);
830
+ if (options?.updatedFrom) params.set("updated_from", options.updatedFrom);
831
+ if (options?.updatedTo) params.set("updated_to", options.updatedTo);
832
+ const response = await client.get(
833
+ `/charts${params.toString() ? `?${params.toString()}` : ""}`,
834
+ tenantId,
835
+ options?.userId,
836
+ options?.scopes,
837
+ signal
838
+ );
839
+ if (options?.includeData) {
840
+ response.data = await Promise.all(
841
+ response.data.map(async (chart) => ({
842
+ ...chart,
843
+ vega_lite_spec: {
844
+ ...chart.vega_lite_spec,
845
+ data: {
846
+ values: await queryEngine.execute(
847
+ chart.sql,
848
+ chart.sql_params ?? void 0,
849
+ chart.target_db ?? void 0
850
+ )
851
+ }
852
+ }
853
+ }))
854
+ );
855
+ }
856
+ return response;
857
+ }
858
+ async function getChart(client, queryEngine, id, options, signal) {
859
+ const tenantId = resolveTenantId(client, options?.tenantId);
860
+ const chart = await client.get(
861
+ `/charts/${encodeURIComponent(id)}`,
862
+ tenantId,
863
+ options?.userId,
864
+ options?.scopes,
865
+ signal
866
+ );
867
+ return {
868
+ ...chart,
869
+ vega_lite_spec: {
870
+ ...chart.vega_lite_spec,
871
+ data: {
872
+ values: await queryEngine.execute(
873
+ chart.sql,
874
+ chart.sql_params ?? void 0,
875
+ chart.target_db ?? void 0
876
+ )
877
+ }
878
+ }
879
+ };
880
+ }
881
+ async function updateChart(client, id, body, options, signal) {
882
+ const tenantId = resolveTenantId(client, options?.tenantId);
883
+ return await client.put(
884
+ `/charts/${encodeURIComponent(id)}`,
885
+ body,
886
+ tenantId,
887
+ options?.userId,
888
+ options?.scopes,
889
+ signal
890
+ );
891
+ }
892
+ async function deleteChart(client, id, options, signal) {
893
+ const tenantId = resolveTenantId(client, options?.tenantId);
894
+ await client.delete(
895
+ `/charts/${encodeURIComponent(id)}`,
896
+ tenantId,
897
+ options?.userId,
898
+ options?.scopes,
899
+ signal
900
+ );
901
+ }
902
+ function resolveTenantId(client, tenantId) {
903
+ const resolved = tenantId ?? client.getDefaultTenantId();
904
+ if (!resolved) {
905
+ throw new Error(
906
+ "tenantId is required. Provide it per request or via defaultTenantId option."
907
+ );
908
+ }
909
+ return resolved;
910
+ }
911
+
912
+ // src/routes/active-charts.ts
913
+ async function createActiveChart(client, body, options, signal) {
914
+ const tenantId = resolveTenantId2(client, options?.tenantId);
915
+ return await client.post(
916
+ "/active-charts",
917
+ body,
918
+ tenantId,
919
+ options?.userId,
920
+ options?.scopes,
921
+ signal
922
+ );
923
+ }
924
+ async function listActiveCharts(client, queryEngine, options, signal) {
925
+ const tenantId = resolveTenantId2(client, options?.tenantId);
926
+ const params = new URLSearchParams();
927
+ if (options?.pagination?.page)
928
+ params.set("page", `${options.pagination.page}`);
929
+ if (options?.pagination?.limit)
930
+ params.set("limit", `${options.pagination.limit}`);
931
+ if (options?.sortBy) params.set("sort_by", options.sortBy);
932
+ if (options?.sortDir) params.set("sort_dir", options.sortDir);
933
+ if (options?.title) params.set("name", options.title);
934
+ if (options?.userFilter) params.set("user_id", options.userFilter);
935
+ if (options?.createdFrom) params.set("created_from", options.createdFrom);
936
+ if (options?.createdTo) params.set("created_to", options.createdTo);
937
+ if (options?.updatedFrom) params.set("updated_from", options.updatedFrom);
938
+ if (options?.updatedTo) params.set("updated_to", options.updatedTo);
939
+ const response = await client.get(
940
+ `/active-charts${params.toString() ? `?${params.toString()}` : ""}`,
941
+ tenantId,
942
+ options?.userId,
943
+ options?.scopes,
944
+ signal
945
+ );
946
+ if (options?.withData) {
947
+ response.data = await Promise.all(
948
+ response.data.map(async (active) => ({
949
+ ...active,
950
+ chart: active.chart ? await getChart(
951
+ client,
952
+ queryEngine,
953
+ active.chart_id,
954
+ options,
955
+ signal
956
+ ) : null
957
+ }))
958
+ );
959
+ }
960
+ return response;
961
+ }
962
+ async function getActiveChart(client, queryEngine, id, options, signal) {
963
+ const tenantId = resolveTenantId2(client, options?.tenantId);
964
+ const active = await client.get(
965
+ `/active-charts/${encodeURIComponent(id)}`,
966
+ tenantId,
967
+ options?.userId,
968
+ options?.scopes,
969
+ signal
970
+ );
971
+ if (options?.withData && active.chart_id) {
972
+ return {
973
+ ...active,
974
+ chart: await getChart(
975
+ client,
976
+ queryEngine,
977
+ active.chart_id,
978
+ options,
979
+ signal
980
+ )
981
+ };
982
+ }
983
+ return active;
984
+ }
985
+ async function updateActiveChart(client, id, body, options, signal) {
986
+ const tenantId = resolveTenantId2(client, options?.tenantId);
987
+ return await client.put(
988
+ `/active-charts/${encodeURIComponent(id)}`,
989
+ body,
990
+ tenantId,
991
+ options?.userId,
992
+ options?.scopes,
993
+ signal
994
+ );
995
+ }
996
+ async function deleteActiveChart(client, id, options, signal) {
997
+ const tenantId = resolveTenantId2(client, options?.tenantId);
998
+ await client.delete(
999
+ `/active-charts/${encodeURIComponent(id)}`,
1000
+ tenantId,
1001
+ options?.userId,
1002
+ options?.scopes,
1003
+ signal
1004
+ );
1005
+ }
1006
+ function resolveTenantId2(client, tenantId) {
1007
+ const resolved = tenantId ?? client.getDefaultTenantId();
1008
+ if (!resolved) {
1009
+ throw new Error(
1010
+ "tenantId is required. Provide it per request or via defaultTenantId option."
1011
+ );
1012
+ }
1013
+ return resolved;
1014
+ }
1015
+
1016
+ // src/routes/ingest.ts
1017
+ var import_node_crypto2 = require("crypto");
1018
+ async function syncSchema(client, queryEngine, databaseName, options, signal) {
1019
+ const tenantId = resolveTenantId3(client, options.tenantId);
1020
+ const adapter = queryEngine.getDatabase(databaseName);
1021
+ const introspection = await adapter.introspect(
1022
+ options.tables ? { tables: options.tables } : void 0
1023
+ );
1024
+ const payload = buildSchemaRequest(databaseName, adapter, introspection);
1025
+ const sessionId = (0, import_node_crypto2.randomUUID)();
1026
+ const response = await client.post(
1027
+ "/ingest",
1028
+ payload,
1029
+ tenantId,
1030
+ options.userId,
1031
+ options.scopes,
1032
+ signal,
1033
+ sessionId
1034
+ );
1035
+ return response;
1036
+ }
1037
+ function resolveTenantId3(client, tenantId) {
1038
+ const resolved = tenantId ?? client.getDefaultTenantId();
1039
+ if (!resolved) {
1040
+ throw new Error(
1041
+ "tenantId is required. Provide it per request or via defaultTenantId option."
1042
+ );
1043
+ }
1044
+ return resolved;
1045
+ }
1046
+ function buildSchemaRequest(databaseName, adapter, introspection) {
1047
+ const dialect = adapter.getDialect();
1048
+ const tables = introspection.tables.map((table) => ({
1049
+ table_name: table.name,
1050
+ description: table.comment ?? `Table ${table.name}`,
1051
+ columns: table.columns.map((column) => ({
1052
+ name: column.name,
1053
+ data_type: column.rawType ?? column.type,
1054
+ is_primary_key: Boolean(column.isPrimaryKey),
1055
+ description: column.comment ?? ""
1056
+ }))
1057
+ }));
1058
+ return {
1059
+ database: databaseName,
1060
+ dialect,
1061
+ tables
1062
+ };
1063
+ }
1064
+
1065
+ // src/routes/query.ts
1066
+ var import_node_crypto3 = require("crypto");
1067
+ async function ask(client, queryEngine, question, options, signal) {
1068
+ const tenantId = resolveTenantId4(client, options.tenantId);
1069
+ const sessionId = (0, import_node_crypto3.randomUUID)();
1070
+ const maxRetry = options.maxRetry ?? 0;
1071
+ let attempt = 0;
1072
+ let lastError = options.lastError;
1073
+ let previousSql = options.previousSql;
1074
+ while (attempt <= maxRetry) {
1075
+ console.log({ lastError, previousSql });
1076
+ const queryResponse = await client.post(
1077
+ "/query",
1078
+ {
1079
+ question,
1080
+ ...lastError ? { last_error: lastError } : {},
1081
+ ...previousSql ? { previous_sql: previousSql } : {},
1082
+ ...options.maxRetry ? { max_retry: options.maxRetry } : {}
1083
+ },
1084
+ tenantId,
1085
+ options.userId,
1086
+ options.scopes,
1087
+ signal,
1088
+ sessionId
1089
+ );
1090
+ const databaseName = queryResponse.database ?? options.database ?? queryEngine.getDefaultDatabase();
1091
+ if (!databaseName) {
1092
+ throw new Error(
1093
+ "No database attached. Call attachPostgres/attachClickhouse first."
1094
+ );
1095
+ }
1096
+ const paramMetadata = Array.isArray(queryResponse.params) ? queryResponse.params : [];
1097
+ const paramValues = queryEngine.mapGeneratedParams(paramMetadata);
1439
1098
  try {
1440
- const adapter = this.getDatabase(database);
1441
- const result = await adapter.execute(sql, params);
1442
- return result.rows;
1099
+ const execution = await queryEngine.validateAndExecute(
1100
+ queryResponse.sql,
1101
+ paramValues,
1102
+ databaseName,
1103
+ tenantId
1104
+ );
1105
+ const rows = execution.rows ?? [];
1106
+ let chart = {
1107
+ vegaLiteSpec: null,
1108
+ notes: rows.length === 0 ? "Query returned no rows." : null
1109
+ };
1110
+ if (rows.length > 0) {
1111
+ const chartResponse = await client.post(
1112
+ "/chart",
1113
+ {
1114
+ question,
1115
+ sql: queryResponse.sql,
1116
+ rationale: queryResponse.rationale,
1117
+ fields: execution.fields,
1118
+ rows: anonymizeResults(rows),
1119
+ max_retries: options.chartMaxRetries ?? 3,
1120
+ query_id: queryResponse.queryId
1121
+ },
1122
+ tenantId,
1123
+ options.userId,
1124
+ options.scopes,
1125
+ signal,
1126
+ sessionId
1127
+ );
1128
+ chart = {
1129
+ vegaLiteSpec: chartResponse.chart ? {
1130
+ ...chartResponse.chart,
1131
+ data: { values: rows }
1132
+ } : null,
1133
+ notes: chartResponse.notes
1134
+ };
1135
+ }
1136
+ return {
1137
+ sql: queryResponse.sql,
1138
+ params: paramValues,
1139
+ paramMetadata,
1140
+ rationale: queryResponse.rationale,
1141
+ dialect: queryResponse.dialect,
1142
+ queryId: queryResponse.queryId,
1143
+ rows,
1144
+ fields: execution.fields,
1145
+ chart,
1146
+ context: queryResponse.context,
1147
+ attempts: attempt + 1
1148
+ };
1443
1149
  } catch (error) {
1150
+ attempt++;
1151
+ if (attempt > maxRetry) {
1152
+ throw error;
1153
+ }
1154
+ lastError = error instanceof Error ? error.message : String(error);
1155
+ previousSql = queryResponse.sql;
1444
1156
  console.warn(
1445
- `Failed to execute SQL locally for database '${database}':`,
1446
- error
1157
+ `SQL execution failed (attempt ${attempt}/${maxRetry + 1}): ${lastError}. Retrying...`
1447
1158
  );
1448
- return [];
1449
1159
  }
1450
1160
  }
1451
- };
1161
+ throw new Error("Unexpected error in ask retry loop");
1162
+ }
1163
+ function resolveTenantId4(client, tenantId) {
1164
+ const resolved = tenantId ?? client.getDefaultTenantId();
1165
+ if (!resolved) {
1166
+ throw new Error(
1167
+ "tenantId is required. Provide it per request or via defaultTenantId option."
1168
+ );
1169
+ }
1170
+ return resolved;
1171
+ }
1452
1172
  function anonymizeResults(rows) {
1453
1173
  if (!rows?.length) return [];
1454
1174
  return rows.map((row) => {
@@ -1461,6 +1181,144 @@ function anonymizeResults(rows) {
1461
1181
  return masked;
1462
1182
  });
1463
1183
  }
1184
+
1185
+ // src/index.ts
1186
+ var QueryPanelSdkAPI = class {
1187
+ client;
1188
+ queryEngine;
1189
+ constructor(baseUrl, privateKey, organizationId, options) {
1190
+ this.client = new ApiClient(baseUrl, privateKey, organizationId, options);
1191
+ this.queryEngine = new QueryEngine();
1192
+ }
1193
+ // Database attachment methods
1194
+ attachClickhouse(name, clientFn, options) {
1195
+ const adapter = new ClickHouseAdapter(clientFn, options);
1196
+ const metadata = {
1197
+ name,
1198
+ dialect: "clickhouse",
1199
+ description: options?.description,
1200
+ tags: options?.tags,
1201
+ tenantFieldName: options?.tenantFieldName,
1202
+ tenantFieldType: options?.tenantFieldType ?? "String",
1203
+ enforceTenantIsolation: options?.tenantFieldName ? options?.enforceTenantIsolation ?? true : void 0
1204
+ };
1205
+ this.queryEngine.attachDatabase(name, adapter, metadata);
1206
+ }
1207
+ attachPostgres(name, clientFn, options) {
1208
+ const adapter = new PostgresAdapter(clientFn, options);
1209
+ const metadata = {
1210
+ name,
1211
+ dialect: "postgres",
1212
+ description: options?.description,
1213
+ tags: options?.tags,
1214
+ tenantFieldName: options?.tenantFieldName,
1215
+ enforceTenantIsolation: options?.tenantFieldName ? options?.enforceTenantIsolation ?? true : void 0
1216
+ };
1217
+ this.queryEngine.attachDatabase(name, adapter, metadata);
1218
+ }
1219
+ attachDatabase(name, adapter) {
1220
+ const metadata = {
1221
+ name,
1222
+ dialect: adapter.getDialect()
1223
+ };
1224
+ this.queryEngine.attachDatabase(name, adapter, metadata);
1225
+ }
1226
+ // Schema introspection and sync
1227
+ async introspect(databaseName, tables) {
1228
+ const adapter = this.queryEngine.getDatabase(databaseName);
1229
+ return await adapter.introspect(tables ? { tables } : void 0);
1230
+ }
1231
+ async syncSchema(databaseName, options, signal) {
1232
+ return await syncSchema(
1233
+ this.client,
1234
+ this.queryEngine,
1235
+ databaseName,
1236
+ options,
1237
+ signal
1238
+ );
1239
+ }
1240
+ // Natural language query
1241
+ async ask(question, options, signal) {
1242
+ return await ask(
1243
+ this.client,
1244
+ this.queryEngine,
1245
+ question,
1246
+ options,
1247
+ signal
1248
+ );
1249
+ }
1250
+ // Chart CRUD operations
1251
+ async createChart(body, options, signal) {
1252
+ return await createChart(this.client, body, options, signal);
1253
+ }
1254
+ async listCharts(options, signal) {
1255
+ return await listCharts(
1256
+ this.client,
1257
+ this.queryEngine,
1258
+ options,
1259
+ signal
1260
+ );
1261
+ }
1262
+ async getChart(id, options, signal) {
1263
+ return await getChart(
1264
+ this.client,
1265
+ this.queryEngine,
1266
+ id,
1267
+ options,
1268
+ signal
1269
+ );
1270
+ }
1271
+ async updateChart(id, body, options, signal) {
1272
+ return await updateChart(
1273
+ this.client,
1274
+ id,
1275
+ body,
1276
+ options,
1277
+ signal
1278
+ );
1279
+ }
1280
+ async deleteChart(id, options, signal) {
1281
+ await deleteChart(this.client, id, options, signal);
1282
+ }
1283
+ // Active Chart CRUD operations
1284
+ async createActiveChart(body, options, signal) {
1285
+ return await createActiveChart(
1286
+ this.client,
1287
+ body,
1288
+ options,
1289
+ signal
1290
+ );
1291
+ }
1292
+ async listActiveCharts(options, signal) {
1293
+ return await listActiveCharts(
1294
+ this.client,
1295
+ this.queryEngine,
1296
+ options,
1297
+ signal
1298
+ );
1299
+ }
1300
+ async getActiveChart(id, options, signal) {
1301
+ return await getActiveChart(
1302
+ this.client,
1303
+ this.queryEngine,
1304
+ id,
1305
+ options,
1306
+ signal
1307
+ );
1308
+ }
1309
+ async updateActiveChart(id, body, options, signal) {
1310
+ return await updateActiveChart(
1311
+ this.client,
1312
+ id,
1313
+ body,
1314
+ options,
1315
+ signal
1316
+ );
1317
+ }
1318
+ async deleteActiveChart(id, options, signal) {
1319
+ await deleteActiveChart(this.client, id, options, signal);
1320
+ }
1321
+ };
1464
1322
  // Annotate the CommonJS export names for ESM import in node:
1465
1323
  0 && (module.exports = {
1466
1324
  ClickHouseAdapter,