@querypanel/node-sdk 1.0.25 → 1.0.27

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,38 +547,15 @@ 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_jose = require("jose");
558
+ var ApiClient = class {
815
559
  baseUrl;
816
560
  privateKey;
817
561
  organizationId;
@@ -819,11 +563,6 @@ var QueryPanelSdkAPI = class {
819
563
  additionalHeaders;
820
564
  fetchImpl;
821
565
  cachedPrivateKey;
822
- databases = /* @__PURE__ */ new Map();
823
- databaseMetadata = /* @__PURE__ */ new Map();
824
- defaultDatabase;
825
- lastSyncHashes = /* @__PURE__ */ new Map();
826
- syncedDatabases = /* @__PURE__ */ new Set();
827
566
  constructor(baseUrl, privateKey, organizationId, options) {
828
567
  if (!baseUrl) {
829
568
  throw new Error("Base URL is required");
@@ -846,561 +585,150 @@ var QueryPanelSdkAPI = class {
846
585
  );
847
586
  }
848
587
  }
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
588
+ getDefaultTenantId() {
589
+ return this.defaultTenantId;
590
+ }
591
+ async get(path, tenantId, userId, scopes, signal, sessionId) {
592
+ return await this.request(path, {
593
+ method: "GET",
594
+ headers: await this.buildHeaders(tenantId, userId, scopes, false, sessionId),
595
+ signal
596
+ });
597
+ }
598
+ async post(path, body, tenantId, userId, scopes, signal, sessionId) {
599
+ return await this.request(path, {
600
+ method: "POST",
601
+ headers: await this.buildHeaders(tenantId, userId, scopes, true, sessionId),
602
+ body: JSON.stringify(body ?? {}),
603
+ signal
604
+ });
605
+ }
606
+ async put(path, body, tenantId, userId, scopes, signal, sessionId) {
607
+ return await this.request(path, {
608
+ method: "PUT",
609
+ headers: await this.buildHeaders(tenantId, userId, scopes, true, sessionId),
610
+ body: JSON.stringify(body ?? {}),
611
+ signal
612
+ });
613
+ }
614
+ async delete(path, tenantId, userId, scopes, signal, sessionId) {
615
+ return await this.request(path, {
616
+ method: "DELETE",
617
+ headers: await this.buildHeaders(tenantId, userId, scopes, false, sessionId),
618
+ signal
619
+ });
620
+ }
621
+ async request(path, init) {
622
+ const response = await this.fetchImpl(`${this.baseUrl}${path}`, init);
623
+ const text = await response.text();
624
+ let json;
625
+ try {
626
+ json = text ? JSON.parse(text) : void 0;
627
+ } catch {
628
+ json = void 0;
629
+ }
630
+ if (!response.ok) {
631
+ const error = new Error(
632
+ json?.error || response.statusText || "Request failed"
633
+ );
634
+ error.status = response.status;
635
+ if (json?.details) error.details = json.details;
636
+ throw error;
637
+ }
638
+ return json;
639
+ }
640
+ async buildHeaders(tenantId, userId, scopes, includeJson = true, sessionId) {
641
+ const token = await this.generateJWT(tenantId, userId, scopes);
642
+ const headers = {
643
+ Authorization: `Bearer ${token}`,
644
+ Accept: "application/json"
860
645
  };
861
- this.databaseMetadata.set(name, metadata);
646
+ if (includeJson) {
647
+ headers["Content-Type"] = "application/json";
648
+ }
649
+ if (sessionId) {
650
+ headers["x-session-id"] = sessionId;
651
+ }
652
+ if (this.additionalHeaders) {
653
+ Object.assign(headers, this.additionalHeaders);
654
+ }
655
+ return headers;
862
656
  }
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
657
+ async generateJWT(tenantId, userId, scopes) {
658
+ if (!this.cachedPrivateKey) {
659
+ this.cachedPrivateKey = await (0, import_jose.importPKCS8)(this.privateKey, "RS256");
660
+ }
661
+ const payload = {
662
+ organizationId: this.organizationId,
663
+ tenantId
873
664
  };
874
- this.databaseMetadata.set(name, metadata);
665
+ if (userId) payload.userId = userId;
666
+ if (scopes?.length) payload.scopes = scopes;
667
+ return await new import_jose.SignJWT(payload).setProtectedHeader({ alg: "RS256" }).setIssuedAt().setExpirationTime("1h").sign(this.cachedPrivateKey);
875
668
  }
876
- attachDatabase(name, adapter) {
669
+ };
670
+
671
+ // src/core/query-engine.ts
672
+ var QueryEngine = class {
673
+ databases = /* @__PURE__ */ new Map();
674
+ databaseMetadata = /* @__PURE__ */ new Map();
675
+ defaultDatabase;
676
+ attachDatabase(name, adapter, metadata) {
877
677
  this.databases.set(name, adapter);
678
+ this.databaseMetadata.set(name, metadata);
878
679
  if (!this.defaultDatabase) {
879
680
  this.defaultDatabase = name;
880
681
  }
881
682
  }
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
- };
683
+ getDatabase(name) {
684
+ const dbName = name ?? this.defaultDatabase;
685
+ if (!dbName) {
686
+ throw new Error("No database attached.");
904
687
  }
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) {
688
+ const adapter = this.databases.get(dbName);
689
+ if (!adapter) {
967
690
  throw new Error(
968
- "No database attached. Call attachPostgres/attachClickhouse first."
691
+ `Database '${dbName}' not found. Attached: ${Array.from(
692
+ this.databases.keys()
693
+ ).join(", ")}`
969
694
  );
970
695
  }
696
+ return adapter;
697
+ }
698
+ getDatabaseMetadata(name) {
699
+ const dbName = name ?? this.defaultDatabase;
700
+ if (!dbName) return void 0;
701
+ return this.databaseMetadata.get(dbName);
702
+ }
703
+ getDefaultDatabase() {
704
+ return this.defaultDatabase;
705
+ }
706
+ async validateAndExecute(sql, params, databaseName, tenantId) {
971
707
  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);
708
+ const metadata = this.getDatabaseMetadata(databaseName);
709
+ let finalSql = sql;
975
710
  if (metadata) {
976
- queryResponse.sql = this.ensureTenantIsolation(
977
- queryResponse.sql,
978
- paramValues,
979
- metadata,
980
- tenantId
981
- );
982
- }
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
989
- };
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
1007
- );
1008
- chart = {
1009
- vegaLiteSpec: chartResponse.chart ? {
1010
- ...chartResponse.chart,
1011
- data: { values: rows }
1012
- } : null,
1013
- notes: chartResponse.notes
1014
- };
711
+ finalSql = this.ensureTenantIsolation(sql, params, metadata, tenantId);
1015
712
  }
713
+ await adapter.validate(finalSql, params);
714
+ const result = await adapter.execute(finalSql, params);
1016
715
  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
716
+ rows: result.rows,
717
+ fields: result.fields
1027
718
  };
1028
719
  }
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");
720
+ async execute(sql, params, databaseName) {
721
+ try {
722
+ const adapter = this.getDatabase(databaseName);
723
+ const result = await adapter.execute(sql, params);
724
+ return result.rows;
725
+ } catch (error) {
726
+ console.warn(
727
+ `Failed to execute SQL locally for database '${databaseName}':`,
728
+ error
729
+ );
730
+ return [];
731
+ }
1404
732
  }
1405
733
  mapGeneratedParams(params) {
1406
734
  const record = {};
@@ -1435,20 +763,375 @@ var QueryPanelSdkAPI = class {
1435
763
  }
1436
764
  return `${sql} WHERE ${tenantPredicate}`;
1437
765
  }
1438
- async runSafeQueryOnClient(sql, database, params) {
766
+ };
767
+
768
+ // src/routes/charts.ts
769
+ async function createChart(client, body, options, signal) {
770
+ const tenantId = resolveTenantId(client, options?.tenantId);
771
+ return await client.post(
772
+ "/charts",
773
+ body,
774
+ tenantId,
775
+ options?.userId,
776
+ options?.scopes,
777
+ signal
778
+ );
779
+ }
780
+ async function listCharts(client, queryEngine, options, signal) {
781
+ const tenantId = resolveTenantId(client, options?.tenantId);
782
+ const params = new URLSearchParams();
783
+ if (options?.pagination?.page)
784
+ params.set("page", `${options.pagination.page}`);
785
+ if (options?.pagination?.limit)
786
+ params.set("limit", `${options.pagination.limit}`);
787
+ if (options?.sortBy) params.set("sort_by", options.sortBy);
788
+ if (options?.sortDir) params.set("sort_dir", options.sortDir);
789
+ if (options?.title) params.set("title", options.title);
790
+ if (options?.userFilter) params.set("user_id", options.userFilter);
791
+ if (options?.createdFrom) params.set("created_from", options.createdFrom);
792
+ if (options?.createdTo) params.set("created_to", options.createdTo);
793
+ if (options?.updatedFrom) params.set("updated_from", options.updatedFrom);
794
+ if (options?.updatedTo) params.set("updated_to", options.updatedTo);
795
+ const response = await client.get(
796
+ `/charts${params.toString() ? `?${params.toString()}` : ""}`,
797
+ tenantId,
798
+ options?.userId,
799
+ options?.scopes,
800
+ signal
801
+ );
802
+ if (options?.includeData) {
803
+ response.data = await Promise.all(
804
+ response.data.map(async (chart) => ({
805
+ ...chart,
806
+ vega_lite_spec: {
807
+ ...chart.vega_lite_spec,
808
+ data: {
809
+ values: await queryEngine.execute(
810
+ chart.sql,
811
+ chart.sql_params ?? void 0,
812
+ chart.target_db ?? void 0
813
+ )
814
+ }
815
+ }
816
+ }))
817
+ );
818
+ }
819
+ return response;
820
+ }
821
+ async function getChart(client, queryEngine, id, options, signal) {
822
+ const tenantId = resolveTenantId(client, options?.tenantId);
823
+ const chart = await client.get(
824
+ `/charts/${encodeURIComponent(id)}`,
825
+ tenantId,
826
+ options?.userId,
827
+ options?.scopes,
828
+ signal
829
+ );
830
+ return {
831
+ ...chart,
832
+ vega_lite_spec: {
833
+ ...chart.vega_lite_spec,
834
+ data: {
835
+ values: await queryEngine.execute(
836
+ chart.sql,
837
+ chart.sql_params ?? void 0,
838
+ chart.target_db ?? void 0
839
+ )
840
+ }
841
+ }
842
+ };
843
+ }
844
+ async function updateChart(client, id, body, options, signal) {
845
+ const tenantId = resolveTenantId(client, options?.tenantId);
846
+ return await client.put(
847
+ `/charts/${encodeURIComponent(id)}`,
848
+ body,
849
+ tenantId,
850
+ options?.userId,
851
+ options?.scopes,
852
+ signal
853
+ );
854
+ }
855
+ async function deleteChart(client, id, options, signal) {
856
+ const tenantId = resolveTenantId(client, options?.tenantId);
857
+ await client.delete(
858
+ `/charts/${encodeURIComponent(id)}`,
859
+ tenantId,
860
+ options?.userId,
861
+ options?.scopes,
862
+ signal
863
+ );
864
+ }
865
+ function resolveTenantId(client, tenantId) {
866
+ const resolved = tenantId ?? client.getDefaultTenantId();
867
+ if (!resolved) {
868
+ throw new Error(
869
+ "tenantId is required. Provide it per request or via defaultTenantId option."
870
+ );
871
+ }
872
+ return resolved;
873
+ }
874
+
875
+ // src/routes/active-charts.ts
876
+ async function createActiveChart(client, body, options, signal) {
877
+ const tenantId = resolveTenantId2(client, options?.tenantId);
878
+ return await client.post(
879
+ "/active-charts",
880
+ body,
881
+ tenantId,
882
+ options?.userId,
883
+ options?.scopes,
884
+ signal
885
+ );
886
+ }
887
+ async function listActiveCharts(client, queryEngine, options, signal) {
888
+ const tenantId = resolveTenantId2(client, options?.tenantId);
889
+ const params = new URLSearchParams();
890
+ if (options?.pagination?.page)
891
+ params.set("page", `${options.pagination.page}`);
892
+ if (options?.pagination?.limit)
893
+ params.set("limit", `${options.pagination.limit}`);
894
+ if (options?.sortBy) params.set("sort_by", options.sortBy);
895
+ if (options?.sortDir) params.set("sort_dir", options.sortDir);
896
+ if (options?.title) params.set("name", options.title);
897
+ if (options?.userFilter) params.set("user_id", options.userFilter);
898
+ if (options?.createdFrom) params.set("created_from", options.createdFrom);
899
+ if (options?.createdTo) params.set("created_to", options.createdTo);
900
+ if (options?.updatedFrom) params.set("updated_from", options.updatedFrom);
901
+ if (options?.updatedTo) params.set("updated_to", options.updatedTo);
902
+ const response = await client.get(
903
+ `/active-charts${params.toString() ? `?${params.toString()}` : ""}`,
904
+ tenantId,
905
+ options?.userId,
906
+ options?.scopes,
907
+ signal
908
+ );
909
+ if (options?.withData) {
910
+ response.data = await Promise.all(
911
+ response.data.map(async (active) => ({
912
+ ...active,
913
+ chart: active.chart ? await getChart(
914
+ client,
915
+ queryEngine,
916
+ active.chart_id,
917
+ options,
918
+ signal
919
+ ) : null
920
+ }))
921
+ );
922
+ }
923
+ return response;
924
+ }
925
+ async function getActiveChart(client, queryEngine, id, options, signal) {
926
+ const tenantId = resolveTenantId2(client, options?.tenantId);
927
+ const active = await client.get(
928
+ `/active-charts/${encodeURIComponent(id)}`,
929
+ tenantId,
930
+ options?.userId,
931
+ options?.scopes,
932
+ signal
933
+ );
934
+ if (options?.withData && active.chart_id) {
935
+ return {
936
+ ...active,
937
+ chart: await getChart(
938
+ client,
939
+ queryEngine,
940
+ active.chart_id,
941
+ options,
942
+ signal
943
+ )
944
+ };
945
+ }
946
+ return active;
947
+ }
948
+ async function updateActiveChart(client, id, body, options, signal) {
949
+ const tenantId = resolveTenantId2(client, options?.tenantId);
950
+ return await client.put(
951
+ `/active-charts/${encodeURIComponent(id)}`,
952
+ body,
953
+ tenantId,
954
+ options?.userId,
955
+ options?.scopes,
956
+ signal
957
+ );
958
+ }
959
+ async function deleteActiveChart(client, id, options, signal) {
960
+ const tenantId = resolveTenantId2(client, options?.tenantId);
961
+ await client.delete(
962
+ `/active-charts/${encodeURIComponent(id)}`,
963
+ tenantId,
964
+ options?.userId,
965
+ options?.scopes,
966
+ signal
967
+ );
968
+ }
969
+ function resolveTenantId2(client, tenantId) {
970
+ const resolved = tenantId ?? client.getDefaultTenantId();
971
+ if (!resolved) {
972
+ throw new Error(
973
+ "tenantId is required. Provide it per request or via defaultTenantId option."
974
+ );
975
+ }
976
+ return resolved;
977
+ }
978
+
979
+ // src/routes/ingest.ts
980
+ var import_node_crypto = require("crypto");
981
+ async function syncSchema(client, queryEngine, databaseName, options, signal) {
982
+ const tenantId = resolveTenantId3(client, options.tenantId);
983
+ const adapter = queryEngine.getDatabase(databaseName);
984
+ const introspection = await adapter.introspect(
985
+ options.tables ? { tables: options.tables } : void 0
986
+ );
987
+ const payload = buildSchemaRequest(databaseName, adapter, introspection);
988
+ const sessionId = (0, import_node_crypto.randomUUID)();
989
+ const response = await client.post(
990
+ "/ingest",
991
+ payload,
992
+ tenantId,
993
+ options.userId,
994
+ options.scopes,
995
+ signal,
996
+ sessionId
997
+ );
998
+ return response;
999
+ }
1000
+ function resolveTenantId3(client, tenantId) {
1001
+ const resolved = tenantId ?? client.getDefaultTenantId();
1002
+ if (!resolved) {
1003
+ throw new Error(
1004
+ "tenantId is required. Provide it per request or via defaultTenantId option."
1005
+ );
1006
+ }
1007
+ return resolved;
1008
+ }
1009
+ function buildSchemaRequest(databaseName, adapter, introspection) {
1010
+ const dialect = adapter.getDialect();
1011
+ const tables = introspection.tables.map((table) => ({
1012
+ table_name: table.name,
1013
+ description: table.comment ?? `Table ${table.name}`,
1014
+ columns: table.columns.map((column) => ({
1015
+ name: column.name,
1016
+ data_type: column.rawType ?? column.type,
1017
+ is_primary_key: Boolean(column.isPrimaryKey),
1018
+ description: column.comment ?? ""
1019
+ }))
1020
+ }));
1021
+ return {
1022
+ database: databaseName,
1023
+ dialect,
1024
+ tables
1025
+ };
1026
+ }
1027
+
1028
+ // src/routes/query.ts
1029
+ var import_node_crypto2 = require("crypto");
1030
+ async function ask(client, queryEngine, question, options, signal) {
1031
+ const tenantId = resolveTenantId4(client, options.tenantId);
1032
+ const sessionId = (0, import_node_crypto2.randomUUID)();
1033
+ const maxRetry = options.maxRetry ?? 0;
1034
+ let attempt = 0;
1035
+ let lastError = options.lastError;
1036
+ let previousSql = options.previousSql;
1037
+ while (attempt <= maxRetry) {
1038
+ console.log({ lastError, previousSql });
1039
+ const queryResponse = await client.post(
1040
+ "/query",
1041
+ {
1042
+ question,
1043
+ ...lastError ? { last_error: lastError } : {},
1044
+ ...previousSql ? { previous_sql: previousSql } : {},
1045
+ ...options.maxRetry ? { max_retry: options.maxRetry } : {}
1046
+ },
1047
+ tenantId,
1048
+ options.userId,
1049
+ options.scopes,
1050
+ signal,
1051
+ sessionId
1052
+ );
1053
+ const databaseName = queryResponse.database ?? options.database ?? queryEngine.getDefaultDatabase();
1054
+ if (!databaseName) {
1055
+ throw new Error(
1056
+ "No database attached. Call attachPostgres/attachClickhouse first."
1057
+ );
1058
+ }
1059
+ const paramMetadata = Array.isArray(queryResponse.params) ? queryResponse.params : [];
1060
+ const paramValues = queryEngine.mapGeneratedParams(paramMetadata);
1439
1061
  try {
1440
- const adapter = this.getDatabase(database);
1441
- const result = await adapter.execute(sql, params);
1442
- return result.rows;
1062
+ const execution = await queryEngine.validateAndExecute(
1063
+ queryResponse.sql,
1064
+ paramValues,
1065
+ databaseName,
1066
+ tenantId
1067
+ );
1068
+ const rows = execution.rows ?? [];
1069
+ let chart = {
1070
+ vegaLiteSpec: null,
1071
+ notes: rows.length === 0 ? "Query returned no rows." : null
1072
+ };
1073
+ if (rows.length > 0) {
1074
+ const chartResponse = await client.post(
1075
+ "/chart",
1076
+ {
1077
+ question,
1078
+ sql: queryResponse.sql,
1079
+ rationale: queryResponse.rationale,
1080
+ fields: execution.fields,
1081
+ rows: anonymizeResults(rows),
1082
+ max_retries: options.chartMaxRetries ?? 3,
1083
+ query_id: queryResponse.queryId
1084
+ },
1085
+ tenantId,
1086
+ options.userId,
1087
+ options.scopes,
1088
+ signal,
1089
+ sessionId
1090
+ );
1091
+ chart = {
1092
+ vegaLiteSpec: chartResponse.chart ? {
1093
+ ...chartResponse.chart,
1094
+ data: { values: rows }
1095
+ } : null,
1096
+ notes: chartResponse.notes
1097
+ };
1098
+ }
1099
+ return {
1100
+ sql: queryResponse.sql,
1101
+ params: paramValues,
1102
+ paramMetadata,
1103
+ rationale: queryResponse.rationale,
1104
+ dialect: queryResponse.dialect,
1105
+ queryId: queryResponse.queryId,
1106
+ rows,
1107
+ fields: execution.fields,
1108
+ chart,
1109
+ context: queryResponse.context,
1110
+ attempts: attempt + 1
1111
+ };
1443
1112
  } catch (error) {
1113
+ attempt++;
1114
+ if (attempt > maxRetry) {
1115
+ throw error;
1116
+ }
1117
+ lastError = error instanceof Error ? error.message : String(error);
1118
+ previousSql = queryResponse.sql;
1444
1119
  console.warn(
1445
- `Failed to execute SQL locally for database '${database}':`,
1446
- error
1120
+ `SQL execution failed (attempt ${attempt}/${maxRetry + 1}): ${lastError}. Retrying...`
1447
1121
  );
1448
- return [];
1449
1122
  }
1450
1123
  }
1451
- };
1124
+ throw new Error("Unexpected error in ask retry loop");
1125
+ }
1126
+ function resolveTenantId4(client, tenantId) {
1127
+ const resolved = tenantId ?? client.getDefaultTenantId();
1128
+ if (!resolved) {
1129
+ throw new Error(
1130
+ "tenantId is required. Provide it per request or via defaultTenantId option."
1131
+ );
1132
+ }
1133
+ return resolved;
1134
+ }
1452
1135
  function anonymizeResults(rows) {
1453
1136
  if (!rows?.length) return [];
1454
1137
  return rows.map((row) => {
@@ -1461,6 +1144,144 @@ function anonymizeResults(rows) {
1461
1144
  return masked;
1462
1145
  });
1463
1146
  }
1147
+
1148
+ // src/index.ts
1149
+ var QueryPanelSdkAPI = class {
1150
+ client;
1151
+ queryEngine;
1152
+ constructor(baseUrl, privateKey, organizationId, options) {
1153
+ this.client = new ApiClient(baseUrl, privateKey, organizationId, options);
1154
+ this.queryEngine = new QueryEngine();
1155
+ }
1156
+ // Database attachment methods
1157
+ attachClickhouse(name, clientFn, options) {
1158
+ const adapter = new ClickHouseAdapter(clientFn, options);
1159
+ const metadata = {
1160
+ name,
1161
+ dialect: "clickhouse",
1162
+ description: options?.description,
1163
+ tags: options?.tags,
1164
+ tenantFieldName: options?.tenantFieldName,
1165
+ tenantFieldType: options?.tenantFieldType ?? "String",
1166
+ enforceTenantIsolation: options?.tenantFieldName ? options?.enforceTenantIsolation ?? true : void 0
1167
+ };
1168
+ this.queryEngine.attachDatabase(name, adapter, metadata);
1169
+ }
1170
+ attachPostgres(name, clientFn, options) {
1171
+ const adapter = new PostgresAdapter(clientFn, options);
1172
+ const metadata = {
1173
+ name,
1174
+ dialect: "postgres",
1175
+ description: options?.description,
1176
+ tags: options?.tags,
1177
+ tenantFieldName: options?.tenantFieldName,
1178
+ enforceTenantIsolation: options?.tenantFieldName ? options?.enforceTenantIsolation ?? true : void 0
1179
+ };
1180
+ this.queryEngine.attachDatabase(name, adapter, metadata);
1181
+ }
1182
+ attachDatabase(name, adapter) {
1183
+ const metadata = {
1184
+ name,
1185
+ dialect: adapter.getDialect()
1186
+ };
1187
+ this.queryEngine.attachDatabase(name, adapter, metadata);
1188
+ }
1189
+ // Schema introspection and sync
1190
+ async introspect(databaseName, tables) {
1191
+ const adapter = this.queryEngine.getDatabase(databaseName);
1192
+ return await adapter.introspect(tables ? { tables } : void 0);
1193
+ }
1194
+ async syncSchema(databaseName, options, signal) {
1195
+ return await syncSchema(
1196
+ this.client,
1197
+ this.queryEngine,
1198
+ databaseName,
1199
+ options,
1200
+ signal
1201
+ );
1202
+ }
1203
+ // Natural language query
1204
+ async ask(question, options, signal) {
1205
+ return await ask(
1206
+ this.client,
1207
+ this.queryEngine,
1208
+ question,
1209
+ options,
1210
+ signal
1211
+ );
1212
+ }
1213
+ // Chart CRUD operations
1214
+ async createChart(body, options, signal) {
1215
+ return await createChart(this.client, body, options, signal);
1216
+ }
1217
+ async listCharts(options, signal) {
1218
+ return await listCharts(
1219
+ this.client,
1220
+ this.queryEngine,
1221
+ options,
1222
+ signal
1223
+ );
1224
+ }
1225
+ async getChart(id, options, signal) {
1226
+ return await getChart(
1227
+ this.client,
1228
+ this.queryEngine,
1229
+ id,
1230
+ options,
1231
+ signal
1232
+ );
1233
+ }
1234
+ async updateChart(id, body, options, signal) {
1235
+ return await updateChart(
1236
+ this.client,
1237
+ id,
1238
+ body,
1239
+ options,
1240
+ signal
1241
+ );
1242
+ }
1243
+ async deleteChart(id, options, signal) {
1244
+ await deleteChart(this.client, id, options, signal);
1245
+ }
1246
+ // Active Chart CRUD operations
1247
+ async createActiveChart(body, options, signal) {
1248
+ return await createActiveChart(
1249
+ this.client,
1250
+ body,
1251
+ options,
1252
+ signal
1253
+ );
1254
+ }
1255
+ async listActiveCharts(options, signal) {
1256
+ return await listActiveCharts(
1257
+ this.client,
1258
+ this.queryEngine,
1259
+ options,
1260
+ signal
1261
+ );
1262
+ }
1263
+ async getActiveChart(id, options, signal) {
1264
+ return await getActiveChart(
1265
+ this.client,
1266
+ this.queryEngine,
1267
+ id,
1268
+ options,
1269
+ signal
1270
+ );
1271
+ }
1272
+ async updateActiveChart(id, body, options, signal) {
1273
+ return await updateActiveChart(
1274
+ this.client,
1275
+ id,
1276
+ body,
1277
+ options,
1278
+ signal
1279
+ );
1280
+ }
1281
+ async deleteActiveChart(id, options, signal) {
1282
+ await deleteActiveChart(this.client, id, options, signal);
1283
+ }
1284
+ };
1464
1285
  // Annotate the CommonJS export names for ESM import in node:
1465
1286
  0 && (module.exports = {
1466
1287
  ClickHouseAdapter,