@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 +694 -836
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +138 -218
- package/dist/index.d.ts +138 -218
- package/dist/index.js +694 -838
- package/dist/index.js.map +1 -1
- package/package.json +2 -4
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,
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
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/
|
|
814
|
-
var
|
|
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
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
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
|
-
|
|
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
|
-
|
|
864
|
-
const
|
|
865
|
-
|
|
866
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
883
|
-
const
|
|
884
|
-
|
|
885
|
-
|
|
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
|
|
906
|
-
|
|
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
|
-
|
|
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
|
|
973
|
-
|
|
974
|
-
const metadata = this.databaseMetadata.get(databaseName);
|
|
745
|
+
const metadata = this.getDatabaseMetadata(databaseName);
|
|
746
|
+
let finalSql = sql;
|
|
975
747
|
if (metadata) {
|
|
976
|
-
|
|
977
|
-
queryResponse.sql,
|
|
978
|
-
paramValues,
|
|
979
|
-
metadata,
|
|
980
|
-
tenantId
|
|
981
|
-
);
|
|
748
|
+
finalSql = this.ensureTenantIsolation(sql, params, metadata, tenantId);
|
|
982
749
|
}
|
|
983
|
-
await adapter.validate(
|
|
984
|
-
const
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
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
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
1441
|
-
|
|
1442
|
-
|
|
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
|
-
`
|
|
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,
|