@querypanel/node-sdk 1.0.42 → 1.0.44
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/README.md +127 -0
- package/dist/index.cjs +637 -23
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +518 -3
- package/dist/index.d.ts +518 -3
- package/dist/index.js +635 -23
- package/dist/index.js.map +1 -1
- package/package.json +71 -71
package/dist/index.cjs
CHANGED
|
@@ -32,7 +32,9 @@ var index_exports = {};
|
|
|
32
32
|
__export(index_exports, {
|
|
33
33
|
ClickHouseAdapter: () => ClickHouseAdapter,
|
|
34
34
|
PostgresAdapter: () => PostgresAdapter,
|
|
35
|
+
QueryErrorCode: () => QueryErrorCode,
|
|
35
36
|
QueryPanelSdkAPI: () => QueryPanelSdkAPI,
|
|
37
|
+
QueryPipelineError: () => QueryPipelineError,
|
|
36
38
|
anonymizeResults: () => anonymizeResults
|
|
37
39
|
});
|
|
38
40
|
module.exports = __toCommonJS(index_exports);
|
|
@@ -881,6 +883,57 @@ var QueryEngine = class {
|
|
|
881
883
|
}
|
|
882
884
|
};
|
|
883
885
|
|
|
886
|
+
// src/errors.ts
|
|
887
|
+
var QueryErrorCode = {
|
|
888
|
+
// Moderation errors
|
|
889
|
+
MODERATION_FAILED: "MODERATION_FAILED",
|
|
890
|
+
// Guardrail errors
|
|
891
|
+
RELEVANCE_CHECK_FAILED: "RELEVANCE_CHECK_FAILED",
|
|
892
|
+
SECURITY_CHECK_FAILED: "SECURITY_CHECK_FAILED",
|
|
893
|
+
// SQL generation errors
|
|
894
|
+
SQL_GENERATION_FAILED: "SQL_GENERATION_FAILED",
|
|
895
|
+
// SQL validation errors
|
|
896
|
+
SQL_VALIDATION_FAILED: "SQL_VALIDATION_FAILED",
|
|
897
|
+
// Context retrieval errors
|
|
898
|
+
CONTEXT_RETRIEVAL_FAILED: "CONTEXT_RETRIEVAL_FAILED",
|
|
899
|
+
// General errors
|
|
900
|
+
INTERNAL_ERROR: "INTERNAL_ERROR",
|
|
901
|
+
AUTHENTICATION_REQUIRED: "AUTHENTICATION_REQUIRED",
|
|
902
|
+
VALIDATION_ERROR: "VALIDATION_ERROR"
|
|
903
|
+
};
|
|
904
|
+
var QueryPipelineError = class extends Error {
|
|
905
|
+
constructor(message, code, details) {
|
|
906
|
+
super(message);
|
|
907
|
+
this.code = code;
|
|
908
|
+
this.details = details;
|
|
909
|
+
this.name = "QueryPipelineError";
|
|
910
|
+
}
|
|
911
|
+
/**
|
|
912
|
+
* Check if this is a moderation error
|
|
913
|
+
*/
|
|
914
|
+
isModeration() {
|
|
915
|
+
return this.code === QueryErrorCode.MODERATION_FAILED;
|
|
916
|
+
}
|
|
917
|
+
/**
|
|
918
|
+
* Check if this is a relevance error (question not related to database)
|
|
919
|
+
*/
|
|
920
|
+
isRelevanceError() {
|
|
921
|
+
return this.code === QueryErrorCode.RELEVANCE_CHECK_FAILED;
|
|
922
|
+
}
|
|
923
|
+
/**
|
|
924
|
+
* Check if this is a security error (SQL injection, prompt injection, etc.)
|
|
925
|
+
*/
|
|
926
|
+
isSecurityError() {
|
|
927
|
+
return this.code === QueryErrorCode.SECURITY_CHECK_FAILED;
|
|
928
|
+
}
|
|
929
|
+
/**
|
|
930
|
+
* Check if this is any guardrail error (relevance or security)
|
|
931
|
+
*/
|
|
932
|
+
isGuardrailError() {
|
|
933
|
+
return this.isRelevanceError() || this.isSecurityError();
|
|
934
|
+
}
|
|
935
|
+
};
|
|
936
|
+
|
|
884
937
|
// src/routes/charts.ts
|
|
885
938
|
async function createChart(client, body, options, signal) {
|
|
886
939
|
const tenantId = resolveTenantId(client, options?.tenantId);
|
|
@@ -917,15 +970,10 @@ async function listCharts(client, queryEngine, options, signal) {
|
|
|
917
970
|
);
|
|
918
971
|
if (options?.includeData) {
|
|
919
972
|
response.data = await Promise.all(
|
|
920
|
-
response.data.map(async (chart) =>
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
data: {
|
|
925
|
-
values: await executeChartQuery(queryEngine, chart, tenantId)
|
|
926
|
-
}
|
|
927
|
-
}
|
|
928
|
-
}))
|
|
973
|
+
response.data.map(async (chart) => {
|
|
974
|
+
const rows = await executeChartQuery(queryEngine, chart, tenantId);
|
|
975
|
+
return hydrateChartWithData(chart, rows);
|
|
976
|
+
})
|
|
929
977
|
);
|
|
930
978
|
}
|
|
931
979
|
return response;
|
|
@@ -939,15 +987,8 @@ async function getChart(client, queryEngine, id, options, signal) {
|
|
|
939
987
|
options?.scopes,
|
|
940
988
|
signal
|
|
941
989
|
);
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
vega_lite_spec: {
|
|
945
|
-
...chart.vega_lite_spec,
|
|
946
|
-
data: {
|
|
947
|
-
values: await executeChartQuery(queryEngine, chart, tenantId)
|
|
948
|
-
}
|
|
949
|
-
}
|
|
950
|
-
};
|
|
990
|
+
const rows = await executeChartQuery(queryEngine, chart, tenantId);
|
|
991
|
+
return hydrateChartWithData(chart, rows);
|
|
951
992
|
}
|
|
952
993
|
async function updateChart(client, id, body, options, signal) {
|
|
953
994
|
const tenantId = resolveTenantId(client, options?.tenantId);
|
|
@@ -998,6 +1039,31 @@ async function executeChartQuery(queryEngine, chart, tenantId) {
|
|
|
998
1039
|
return [];
|
|
999
1040
|
}
|
|
1000
1041
|
}
|
|
1042
|
+
function hydrateChartWithData(chart, rows) {
|
|
1043
|
+
const spec = chart.vega_lite_spec;
|
|
1044
|
+
if (chart.spec_type === "vizspec") {
|
|
1045
|
+
const existingData = spec.data ?? {};
|
|
1046
|
+
return {
|
|
1047
|
+
...chart,
|
|
1048
|
+
vega_lite_spec: {
|
|
1049
|
+
...spec,
|
|
1050
|
+
data: {
|
|
1051
|
+
...existingData,
|
|
1052
|
+
values: rows
|
|
1053
|
+
}
|
|
1054
|
+
}
|
|
1055
|
+
};
|
|
1056
|
+
}
|
|
1057
|
+
return {
|
|
1058
|
+
...chart,
|
|
1059
|
+
vega_lite_spec: {
|
|
1060
|
+
...spec,
|
|
1061
|
+
data: {
|
|
1062
|
+
values: rows
|
|
1063
|
+
}
|
|
1064
|
+
}
|
|
1065
|
+
};
|
|
1066
|
+
}
|
|
1001
1067
|
|
|
1002
1068
|
// src/routes/active-charts.ts
|
|
1003
1069
|
async function createActiveChart(client, body, options, signal) {
|
|
@@ -1164,6 +1230,9 @@ function buildSchemaRequest(databaseName, adapter, introspection, metadata) {
|
|
|
1164
1230
|
return request;
|
|
1165
1231
|
}
|
|
1166
1232
|
|
|
1233
|
+
// src/routes/modify.ts
|
|
1234
|
+
var import_node_crypto4 = __toESM(require("crypto"), 1);
|
|
1235
|
+
|
|
1167
1236
|
// src/routes/query.ts
|
|
1168
1237
|
var import_node_crypto3 = __toESM(require("crypto"), 1);
|
|
1169
1238
|
async function ask(client, queryEngine, question, options, signal) {
|
|
@@ -1202,6 +1271,13 @@ async function ask(client, queryEngine, question, options, signal) {
|
|
|
1202
1271
|
signal,
|
|
1203
1272
|
sessionId
|
|
1204
1273
|
);
|
|
1274
|
+
if (!queryResponse.success) {
|
|
1275
|
+
throw new QueryPipelineError(
|
|
1276
|
+
queryResponse.error || "Query generation failed",
|
|
1277
|
+
queryResponse.code || "INTERNAL_ERROR",
|
|
1278
|
+
queryResponse.details
|
|
1279
|
+
);
|
|
1280
|
+
}
|
|
1205
1281
|
const dbName = queryResponse.database ?? options.database ?? queryEngine.getDefaultDatabase();
|
|
1206
1282
|
if (!dbName) {
|
|
1207
1283
|
throw new Error(
|
|
@@ -1325,11 +1401,216 @@ function anonymizeResults(rows) {
|
|
|
1325
1401
|
});
|
|
1326
1402
|
}
|
|
1327
1403
|
|
|
1328
|
-
// src/routes/
|
|
1329
|
-
|
|
1330
|
-
|
|
1404
|
+
// src/routes/modify.ts
|
|
1405
|
+
function buildModifiedQuestion(originalQuestion, modifications) {
|
|
1406
|
+
const hints = [];
|
|
1407
|
+
if (modifications.timeGranularity) {
|
|
1408
|
+
hints.push(`group results by ${modifications.timeGranularity}`);
|
|
1409
|
+
}
|
|
1410
|
+
if (modifications.dateRange) {
|
|
1411
|
+
const parts = [];
|
|
1412
|
+
if (modifications.dateRange.from) {
|
|
1413
|
+
parts.push(`from ${modifications.dateRange.from}`);
|
|
1414
|
+
}
|
|
1415
|
+
if (modifications.dateRange.to) {
|
|
1416
|
+
parts.push(`to ${modifications.dateRange.to}`);
|
|
1417
|
+
}
|
|
1418
|
+
if (parts.length > 0) {
|
|
1419
|
+
hints.push(`filter date range ${parts.join(" ")}`);
|
|
1420
|
+
}
|
|
1421
|
+
}
|
|
1422
|
+
if (modifications.additionalInstructions) {
|
|
1423
|
+
hints.push(modifications.additionalInstructions);
|
|
1424
|
+
}
|
|
1425
|
+
if (hints.length === 0) {
|
|
1426
|
+
return originalQuestion;
|
|
1427
|
+
}
|
|
1428
|
+
return `${originalQuestion} (${hints.join(", ")})`;
|
|
1429
|
+
}
|
|
1430
|
+
function buildVizHints(modifications) {
|
|
1431
|
+
const hints = {};
|
|
1432
|
+
if (modifications.chartType) {
|
|
1433
|
+
hints.chartType = modifications.chartType;
|
|
1434
|
+
}
|
|
1435
|
+
if (modifications.xAxis) {
|
|
1436
|
+
hints.xAxis = modifications.xAxis;
|
|
1437
|
+
}
|
|
1438
|
+
if (modifications.yAxis) {
|
|
1439
|
+
hints.yAxis = modifications.yAxis;
|
|
1440
|
+
}
|
|
1441
|
+
if (modifications.series) {
|
|
1442
|
+
hints.series = modifications.series;
|
|
1443
|
+
}
|
|
1444
|
+
if (modifications.stacking) {
|
|
1445
|
+
hints.stacking = modifications.stacking;
|
|
1446
|
+
}
|
|
1447
|
+
if (modifications.limit !== void 0) {
|
|
1448
|
+
hints.limit = modifications.limit;
|
|
1449
|
+
}
|
|
1450
|
+
return hints;
|
|
1451
|
+
}
|
|
1452
|
+
function resolveTenantId5(client, tenantId) {
|
|
1453
|
+
const resolved = tenantId ?? client.getDefaultTenantId();
|
|
1454
|
+
if (!resolved) {
|
|
1455
|
+
throw new Error(
|
|
1456
|
+
"tenantId is required. Provide it per request or via defaultTenantId option."
|
|
1457
|
+
);
|
|
1458
|
+
}
|
|
1459
|
+
return resolved;
|
|
1460
|
+
}
|
|
1461
|
+
async function modifyChart(client, queryEngine, input, options, signal) {
|
|
1331
1462
|
const tenantId = resolveTenantId5(client, options?.tenantId);
|
|
1332
1463
|
const sessionId = import_node_crypto4.default.randomUUID();
|
|
1464
|
+
const chartType = options?.chartType ?? "vega-lite";
|
|
1465
|
+
const hasSqlMods = !!input.sqlModifications;
|
|
1466
|
+
const hasVizMods = !!input.vizModifications;
|
|
1467
|
+
const hasCustomSql = !!input.sqlModifications?.customSql;
|
|
1468
|
+
let finalSql = input.sql;
|
|
1469
|
+
let finalParams = input.params ?? {};
|
|
1470
|
+
let paramMetadata = [];
|
|
1471
|
+
let rationale;
|
|
1472
|
+
let queryId;
|
|
1473
|
+
let sqlChanged = false;
|
|
1474
|
+
const databaseName = input.database ?? queryEngine.getDefaultDatabase();
|
|
1475
|
+
if (!databaseName) {
|
|
1476
|
+
throw new Error(
|
|
1477
|
+
"No database specified. Provide database in input or attach a default database."
|
|
1478
|
+
);
|
|
1479
|
+
}
|
|
1480
|
+
const metadata = queryEngine.getDatabaseMetadata(databaseName);
|
|
1481
|
+
let tenantSettings;
|
|
1482
|
+
if (metadata?.tenantFieldName) {
|
|
1483
|
+
tenantSettings = {
|
|
1484
|
+
tenantFieldName: metadata.tenantFieldName,
|
|
1485
|
+
tenantFieldType: metadata.tenantFieldType,
|
|
1486
|
+
enforceTenantIsolation: metadata.enforceTenantIsolation
|
|
1487
|
+
};
|
|
1488
|
+
}
|
|
1489
|
+
if (hasCustomSql) {
|
|
1490
|
+
finalSql = input.sqlModifications.customSql;
|
|
1491
|
+
finalParams = {};
|
|
1492
|
+
paramMetadata = [];
|
|
1493
|
+
sqlChanged = true;
|
|
1494
|
+
} else if (hasSqlMods && !hasCustomSql) {
|
|
1495
|
+
const modifiedQuestion = buildModifiedQuestion(
|
|
1496
|
+
input.question,
|
|
1497
|
+
input.sqlModifications
|
|
1498
|
+
);
|
|
1499
|
+
const queryResponse = await client.post(
|
|
1500
|
+
"/query",
|
|
1501
|
+
{
|
|
1502
|
+
question: modifiedQuestion,
|
|
1503
|
+
previous_sql: input.sql,
|
|
1504
|
+
...options?.maxRetry ? { max_retry: options.maxRetry } : {},
|
|
1505
|
+
...tenantSettings ? { tenant_settings: tenantSettings } : {},
|
|
1506
|
+
...databaseName ? { database: databaseName } : {},
|
|
1507
|
+
...metadata?.dialect ? { dialect: metadata.dialect } : {}
|
|
1508
|
+
},
|
|
1509
|
+
tenantId,
|
|
1510
|
+
options?.userId,
|
|
1511
|
+
options?.scopes,
|
|
1512
|
+
signal,
|
|
1513
|
+
sessionId
|
|
1514
|
+
);
|
|
1515
|
+
finalSql = queryResponse.sql;
|
|
1516
|
+
paramMetadata = Array.isArray(queryResponse.params) ? queryResponse.params : [];
|
|
1517
|
+
finalParams = queryEngine.mapGeneratedParams(paramMetadata);
|
|
1518
|
+
rationale = queryResponse.rationale;
|
|
1519
|
+
queryId = queryResponse.queryId;
|
|
1520
|
+
sqlChanged = finalSql !== input.sql;
|
|
1521
|
+
}
|
|
1522
|
+
const execution = await queryEngine.validateAndExecute(
|
|
1523
|
+
finalSql,
|
|
1524
|
+
finalParams,
|
|
1525
|
+
databaseName,
|
|
1526
|
+
tenantId
|
|
1527
|
+
);
|
|
1528
|
+
const rows = execution.rows ?? [];
|
|
1529
|
+
let chart = {
|
|
1530
|
+
specType: chartType,
|
|
1531
|
+
notes: rows.length === 0 ? "Query returned no rows." : null
|
|
1532
|
+
};
|
|
1533
|
+
if (rows.length > 0) {
|
|
1534
|
+
const vizHints = hasVizMods ? buildVizHints(input.vizModifications) : {};
|
|
1535
|
+
if (chartType === "vizspec") {
|
|
1536
|
+
const vizspecResponse = await client.post(
|
|
1537
|
+
"/vizspec",
|
|
1538
|
+
{
|
|
1539
|
+
question: input.question,
|
|
1540
|
+
sql: finalSql,
|
|
1541
|
+
rationale,
|
|
1542
|
+
fields: execution.fields,
|
|
1543
|
+
rows: anonymizeResults(rows),
|
|
1544
|
+
max_retries: options?.chartMaxRetries ?? 3,
|
|
1545
|
+
query_id: queryId,
|
|
1546
|
+
// Include viz hints for the chart generator
|
|
1547
|
+
...hasVizMods ? { encoding_hints: vizHints } : {}
|
|
1548
|
+
},
|
|
1549
|
+
tenantId,
|
|
1550
|
+
options?.userId,
|
|
1551
|
+
options?.scopes,
|
|
1552
|
+
signal,
|
|
1553
|
+
sessionId
|
|
1554
|
+
);
|
|
1555
|
+
chart = {
|
|
1556
|
+
vizSpec: vizspecResponse.spec,
|
|
1557
|
+
specType: "vizspec",
|
|
1558
|
+
notes: vizspecResponse.notes
|
|
1559
|
+
};
|
|
1560
|
+
} else {
|
|
1561
|
+
const chartResponse = await client.post(
|
|
1562
|
+
"/chart",
|
|
1563
|
+
{
|
|
1564
|
+
question: input.question,
|
|
1565
|
+
sql: finalSql,
|
|
1566
|
+
rationale,
|
|
1567
|
+
fields: execution.fields,
|
|
1568
|
+
rows: anonymizeResults(rows),
|
|
1569
|
+
max_retries: options?.chartMaxRetries ?? 3,
|
|
1570
|
+
query_id: queryId,
|
|
1571
|
+
// Include viz hints for the chart generator
|
|
1572
|
+
...hasVizMods ? { encoding_hints: vizHints } : {}
|
|
1573
|
+
},
|
|
1574
|
+
tenantId,
|
|
1575
|
+
options?.userId,
|
|
1576
|
+
options?.scopes,
|
|
1577
|
+
signal,
|
|
1578
|
+
sessionId
|
|
1579
|
+
);
|
|
1580
|
+
chart = {
|
|
1581
|
+
vegaLiteSpec: chartResponse.chart ? {
|
|
1582
|
+
...chartResponse.chart,
|
|
1583
|
+
data: { values: rows }
|
|
1584
|
+
} : null,
|
|
1585
|
+
specType: "vega-lite",
|
|
1586
|
+
notes: chartResponse.notes
|
|
1587
|
+
};
|
|
1588
|
+
}
|
|
1589
|
+
}
|
|
1590
|
+
return {
|
|
1591
|
+
sql: finalSql,
|
|
1592
|
+
params: finalParams,
|
|
1593
|
+
paramMetadata,
|
|
1594
|
+
rationale,
|
|
1595
|
+
dialect: metadata?.dialect ?? "unknown",
|
|
1596
|
+
queryId,
|
|
1597
|
+
rows,
|
|
1598
|
+
fields: execution.fields,
|
|
1599
|
+
chart,
|
|
1600
|
+
attempts: 1,
|
|
1601
|
+
target_db: databaseName,
|
|
1602
|
+
modified: {
|
|
1603
|
+
sqlChanged,
|
|
1604
|
+
vizChanged: hasVizMods
|
|
1605
|
+
}
|
|
1606
|
+
};
|
|
1607
|
+
}
|
|
1608
|
+
|
|
1609
|
+
// src/routes/vizspec.ts
|
|
1610
|
+
var import_node_crypto5 = __toESM(require("crypto"), 1);
|
|
1611
|
+
async function generateVizSpec(client, input, options, signal) {
|
|
1612
|
+
const tenantId = resolveTenantId6(client, options?.tenantId);
|
|
1613
|
+
const sessionId = import_node_crypto5.default.randomUUID();
|
|
1333
1614
|
const response = await client.post(
|
|
1334
1615
|
"/vizspec",
|
|
1335
1616
|
{
|
|
@@ -1349,7 +1630,7 @@ async function generateVizSpec(client, input, options, signal) {
|
|
|
1349
1630
|
);
|
|
1350
1631
|
return response;
|
|
1351
1632
|
}
|
|
1352
|
-
function
|
|
1633
|
+
function resolveTenantId6(client, tenantId) {
|
|
1353
1634
|
const resolved = tenantId ?? client.getDefaultTenantId();
|
|
1354
1635
|
if (!resolved) {
|
|
1355
1636
|
throw new Error(
|
|
@@ -1406,6 +1687,30 @@ var QueryPanelSdkAPI = class {
|
|
|
1406
1687
|
const adapter = this.queryEngine.getDatabase(databaseName);
|
|
1407
1688
|
return await adapter.introspect(tables ? { tables } : void 0);
|
|
1408
1689
|
}
|
|
1690
|
+
/**
|
|
1691
|
+
* Syncs the database schema to QueryPanel for natural language query generation.
|
|
1692
|
+
*
|
|
1693
|
+
* This method introspects your database schema and uploads it to QueryPanel's
|
|
1694
|
+
* vector store. The schema is used by the LLM to generate accurate SQL queries.
|
|
1695
|
+
* Schema embedding is skipped if no changes are detected (drift detection).
|
|
1696
|
+
*
|
|
1697
|
+
* @param databaseName - Name of the attached database to sync
|
|
1698
|
+
* @param options - Sync options including tenantId and forceReindex
|
|
1699
|
+
* @param signal - Optional AbortSignal for cancellation
|
|
1700
|
+
* @returns Response with sync status and chunk counts
|
|
1701
|
+
*
|
|
1702
|
+
* @example
|
|
1703
|
+
* ```typescript
|
|
1704
|
+
* // Basic schema sync (skips if no changes)
|
|
1705
|
+
* await qp.syncSchema("analytics", { tenantId: "tenant_123" });
|
|
1706
|
+
*
|
|
1707
|
+
* // Force re-embedding even if schema hasn't changed
|
|
1708
|
+
* await qp.syncSchema("analytics", {
|
|
1709
|
+
* tenantId: "tenant_123",
|
|
1710
|
+
* forceReindex: true,
|
|
1711
|
+
* });
|
|
1712
|
+
* ```
|
|
1713
|
+
*/
|
|
1409
1714
|
async syncSchema(databaseName, options, signal) {
|
|
1410
1715
|
return await syncSchema(
|
|
1411
1716
|
this.client,
|
|
@@ -1416,6 +1721,36 @@ var QueryPanelSdkAPI = class {
|
|
|
1416
1721
|
);
|
|
1417
1722
|
}
|
|
1418
1723
|
// Natural language query
|
|
1724
|
+
/**
|
|
1725
|
+
* Generates SQL from a natural language question and executes it.
|
|
1726
|
+
*
|
|
1727
|
+
* This is the primary method for converting user questions into data.
|
|
1728
|
+
* It handles the complete flow: SQL generation → validation → execution → chart generation.
|
|
1729
|
+
*
|
|
1730
|
+
* @param question - Natural language question (e.g., "Show revenue by country")
|
|
1731
|
+
* @param options - Query options including tenantId, database, and retry settings
|
|
1732
|
+
* @param signal - Optional AbortSignal for cancellation
|
|
1733
|
+
* @returns Response with SQL, executed data rows, and generated chart
|
|
1734
|
+
* @throws {Error} When SQL generation or execution fails after all retries
|
|
1735
|
+
*
|
|
1736
|
+
* @example
|
|
1737
|
+
* ```typescript
|
|
1738
|
+
* // Basic query
|
|
1739
|
+
* const result = await qp.ask("Top 10 customers by revenue", {
|
|
1740
|
+
* tenantId: "tenant_123",
|
|
1741
|
+
* database: "analytics",
|
|
1742
|
+
* });
|
|
1743
|
+
* console.log(result.sql); // Generated SQL
|
|
1744
|
+
* console.log(result.rows); // Query results
|
|
1745
|
+
* console.log(result.chart); // Vega-Lite chart spec
|
|
1746
|
+
*
|
|
1747
|
+
* // With automatic SQL repair on failure
|
|
1748
|
+
* const result = await qp.ask("Show monthly trends", {
|
|
1749
|
+
* tenantId: "tenant_123",
|
|
1750
|
+
* maxRetry: 3, // Retry up to 3 times if SQL fails
|
|
1751
|
+
* });
|
|
1752
|
+
* ```
|
|
1753
|
+
*/
|
|
1419
1754
|
async ask(question, options, signal) {
|
|
1420
1755
|
return await ask(
|
|
1421
1756
|
this.client,
|
|
@@ -1426,6 +1761,28 @@ var QueryPanelSdkAPI = class {
|
|
|
1426
1761
|
);
|
|
1427
1762
|
}
|
|
1428
1763
|
// VizSpec generation
|
|
1764
|
+
/**
|
|
1765
|
+
* Generates a VizSpec visualization specification from query results.
|
|
1766
|
+
*
|
|
1767
|
+
* Use this when you have raw SQL results and want to generate a chart
|
|
1768
|
+
* specification without going through the full ask() flow. Useful for
|
|
1769
|
+
* re-generating charts with different settings.
|
|
1770
|
+
*
|
|
1771
|
+
* @param input - VizSpec generation input with question, SQL, and result data
|
|
1772
|
+
* @param options - Optional settings for tenant and retries
|
|
1773
|
+
* @param signal - Optional AbortSignal for cancellation
|
|
1774
|
+
* @returns VizSpec specification for chart, table, or metric visualization
|
|
1775
|
+
*
|
|
1776
|
+
* @example
|
|
1777
|
+
* ```typescript
|
|
1778
|
+
* const vizspec = await qp.generateVizSpec({
|
|
1779
|
+
* question: "Revenue by country",
|
|
1780
|
+
* sql: "SELECT country, SUM(revenue) FROM orders GROUP BY country",
|
|
1781
|
+
* fields: ["country", "revenue"],
|
|
1782
|
+
* rows: queryResults,
|
|
1783
|
+
* }, { tenantId: "tenant_123" });
|
|
1784
|
+
* ```
|
|
1785
|
+
*/
|
|
1429
1786
|
async generateVizSpec(input, options, signal) {
|
|
1430
1787
|
return await generateVizSpec(
|
|
1431
1788
|
this.client,
|
|
@@ -1434,10 +1791,125 @@ var QueryPanelSdkAPI = class {
|
|
|
1434
1791
|
signal
|
|
1435
1792
|
);
|
|
1436
1793
|
}
|
|
1794
|
+
// Chart modification
|
|
1795
|
+
/**
|
|
1796
|
+
* Modifies a chart by regenerating SQL and/or applying visualization changes.
|
|
1797
|
+
*
|
|
1798
|
+
* This method supports three modes of operation:
|
|
1799
|
+
*
|
|
1800
|
+
* 1. **SQL Modifications**: When `sqlModifications` is provided, the SQL is
|
|
1801
|
+
* regenerated using the query endpoint with modification hints. If `customSql`
|
|
1802
|
+
* is set, it's used directly without regeneration.
|
|
1803
|
+
*
|
|
1804
|
+
* 2. **Visualization Modifications**: When only `vizModifications` is provided,
|
|
1805
|
+
* the existing SQL is re-executed and a new chart is generated with the
|
|
1806
|
+
* specified encoding preferences.
|
|
1807
|
+
*
|
|
1808
|
+
* 3. **Combined**: Both SQL and visualization modifications can be applied
|
|
1809
|
+
* together. SQL is regenerated first, then viz modifications are applied.
|
|
1810
|
+
*
|
|
1811
|
+
* @param input - Chart modification input with source data and modifications
|
|
1812
|
+
* @param options - Optional settings for tenant, user, and chart generation
|
|
1813
|
+
* @param signal - Optional AbortSignal for cancellation
|
|
1814
|
+
* @returns Modified chart response with SQL, data, and chart specification
|
|
1815
|
+
*
|
|
1816
|
+
* @example
|
|
1817
|
+
* ```typescript
|
|
1818
|
+
* // Change chart type and axis from an ask() response
|
|
1819
|
+
* const modified = await qp.modifyChart({
|
|
1820
|
+
* sql: response.sql,
|
|
1821
|
+
* question: "revenue by country",
|
|
1822
|
+
* database: "analytics",
|
|
1823
|
+
* vizModifications: {
|
|
1824
|
+
* chartType: "bar",
|
|
1825
|
+
* xAxis: { field: "country" },
|
|
1826
|
+
* yAxis: { field: "revenue", aggregate: "sum" },
|
|
1827
|
+
* },
|
|
1828
|
+
* }, { tenantId: "tenant_123" });
|
|
1829
|
+
*
|
|
1830
|
+
* // Change time granularity (triggers SQL regeneration)
|
|
1831
|
+
* const monthly = await qp.modifyChart({
|
|
1832
|
+
* sql: response.sql,
|
|
1833
|
+
* question: "revenue over time",
|
|
1834
|
+
* database: "analytics",
|
|
1835
|
+
* sqlModifications: {
|
|
1836
|
+
* timeGranularity: "month",
|
|
1837
|
+
* dateRange: { from: "2024-01-01", to: "2024-12-31" },
|
|
1838
|
+
* },
|
|
1839
|
+
* }, { tenantId: "tenant_123" });
|
|
1840
|
+
*
|
|
1841
|
+
* // Direct SQL edit with chart regeneration
|
|
1842
|
+
* const customized = await qp.modifyChart({
|
|
1843
|
+
* sql: response.sql,
|
|
1844
|
+
* question: "revenue by country",
|
|
1845
|
+
* database: "analytics",
|
|
1846
|
+
* sqlModifications: {
|
|
1847
|
+
* customSql: "SELECT country, SUM(revenue) FROM orders GROUP BY country",
|
|
1848
|
+
* },
|
|
1849
|
+
* }, { tenantId: "tenant_123" });
|
|
1850
|
+
* ```
|
|
1851
|
+
*/
|
|
1852
|
+
async modifyChart(input, options, signal) {
|
|
1853
|
+
return await modifyChart(
|
|
1854
|
+
this.client,
|
|
1855
|
+
this.queryEngine,
|
|
1856
|
+
input,
|
|
1857
|
+
options,
|
|
1858
|
+
signal
|
|
1859
|
+
);
|
|
1860
|
+
}
|
|
1437
1861
|
// Chart CRUD operations
|
|
1862
|
+
/**
|
|
1863
|
+
* Saves a chart to the QueryPanel system for later retrieval.
|
|
1864
|
+
*
|
|
1865
|
+
* Charts store the SQL query, parameters, and visualization spec - never the actual data.
|
|
1866
|
+
* Data is fetched live when the chart is rendered or refreshed.
|
|
1867
|
+
*
|
|
1868
|
+
* @param body - Chart data including title, SQL, and Vega-Lite spec
|
|
1869
|
+
* @param options - Tenant, user, and scope options
|
|
1870
|
+
* @param signal - Optional AbortSignal for cancellation
|
|
1871
|
+
* @returns The saved chart with its generated ID
|
|
1872
|
+
*
|
|
1873
|
+
* @example
|
|
1874
|
+
* ```typescript
|
|
1875
|
+
* const savedChart = await qp.createChart({
|
|
1876
|
+
* title: "Revenue by Country",
|
|
1877
|
+
* sql: response.sql,
|
|
1878
|
+
* sql_params: response.params,
|
|
1879
|
+
* vega_lite_spec: response.chart.vegaLiteSpec,
|
|
1880
|
+
* target_db: "analytics",
|
|
1881
|
+
* }, { tenantId: "tenant_123", userId: "user_456" });
|
|
1882
|
+
* ```
|
|
1883
|
+
*/
|
|
1438
1884
|
async createChart(body, options, signal) {
|
|
1439
1885
|
return await createChart(this.client, body, options, signal);
|
|
1440
1886
|
}
|
|
1887
|
+
/**
|
|
1888
|
+
* Lists saved charts with optional filtering and pagination.
|
|
1889
|
+
*
|
|
1890
|
+
* Use `includeData: true` to execute each chart's SQL and include live data.
|
|
1891
|
+
*
|
|
1892
|
+
* @param options - Filtering, pagination, and data options
|
|
1893
|
+
* @param signal - Optional AbortSignal for cancellation
|
|
1894
|
+
* @returns Paginated list of charts
|
|
1895
|
+
*
|
|
1896
|
+
* @example
|
|
1897
|
+
* ```typescript
|
|
1898
|
+
* // List charts with pagination
|
|
1899
|
+
* const charts = await qp.listCharts({
|
|
1900
|
+
* tenantId: "tenant_123",
|
|
1901
|
+
* pagination: { page: 1, limit: 10 },
|
|
1902
|
+
* sortBy: "created_at",
|
|
1903
|
+
* sortDir: "desc",
|
|
1904
|
+
* });
|
|
1905
|
+
*
|
|
1906
|
+
* // List with live data
|
|
1907
|
+
* const chartsWithData = await qp.listCharts({
|
|
1908
|
+
* tenantId: "tenant_123",
|
|
1909
|
+
* includeData: true,
|
|
1910
|
+
* });
|
|
1911
|
+
* ```
|
|
1912
|
+
*/
|
|
1441
1913
|
async listCharts(options, signal) {
|
|
1442
1914
|
return await listCharts(
|
|
1443
1915
|
this.client,
|
|
@@ -1446,6 +1918,24 @@ var QueryPanelSdkAPI = class {
|
|
|
1446
1918
|
signal
|
|
1447
1919
|
);
|
|
1448
1920
|
}
|
|
1921
|
+
/**
|
|
1922
|
+
* Retrieves a single chart by ID with live data.
|
|
1923
|
+
*
|
|
1924
|
+
* The chart's SQL is automatically executed and data is included in the response.
|
|
1925
|
+
*
|
|
1926
|
+
* @param id - Chart ID
|
|
1927
|
+
* @param options - Tenant, user, and scope options
|
|
1928
|
+
* @param signal - Optional AbortSignal for cancellation
|
|
1929
|
+
* @returns Chart with live data populated
|
|
1930
|
+
*
|
|
1931
|
+
* @example
|
|
1932
|
+
* ```typescript
|
|
1933
|
+
* const chart = await qp.getChart("chart_123", {
|
|
1934
|
+
* tenantId: "tenant_123",
|
|
1935
|
+
* });
|
|
1936
|
+
* console.log(chart.vega_lite_spec.data.values); // Live data
|
|
1937
|
+
* ```
|
|
1938
|
+
*/
|
|
1449
1939
|
async getChart(id, options, signal) {
|
|
1450
1940
|
return await getChart(
|
|
1451
1941
|
this.client,
|
|
@@ -1455,6 +1945,23 @@ var QueryPanelSdkAPI = class {
|
|
|
1455
1945
|
signal
|
|
1456
1946
|
);
|
|
1457
1947
|
}
|
|
1948
|
+
/**
|
|
1949
|
+
* Updates an existing chart's metadata or configuration.
|
|
1950
|
+
*
|
|
1951
|
+
* @param id - Chart ID to update
|
|
1952
|
+
* @param body - Fields to update (partial update supported)
|
|
1953
|
+
* @param options - Tenant, user, and scope options
|
|
1954
|
+
* @param signal - Optional AbortSignal for cancellation
|
|
1955
|
+
* @returns Updated chart
|
|
1956
|
+
*
|
|
1957
|
+
* @example
|
|
1958
|
+
* ```typescript
|
|
1959
|
+
* const updated = await qp.updateChart("chart_123", {
|
|
1960
|
+
* title: "Updated Chart Title",
|
|
1961
|
+
* description: "New description",
|
|
1962
|
+
* }, { tenantId: "tenant_123" });
|
|
1963
|
+
* ```
|
|
1964
|
+
*/
|
|
1458
1965
|
async updateChart(id, body, options, signal) {
|
|
1459
1966
|
return await updateChart(
|
|
1460
1967
|
this.client,
|
|
@@ -1464,10 +1971,42 @@ var QueryPanelSdkAPI = class {
|
|
|
1464
1971
|
signal
|
|
1465
1972
|
);
|
|
1466
1973
|
}
|
|
1974
|
+
/**
|
|
1975
|
+
* Deletes a chart permanently.
|
|
1976
|
+
*
|
|
1977
|
+
* @param id - Chart ID to delete
|
|
1978
|
+
* @param options - Tenant, user, and scope options
|
|
1979
|
+
* @param signal - Optional AbortSignal for cancellation
|
|
1980
|
+
*
|
|
1981
|
+
* @example
|
|
1982
|
+
* ```typescript
|
|
1983
|
+
* await qp.deleteChart("chart_123", { tenantId: "tenant_123" });
|
|
1984
|
+
* ```
|
|
1985
|
+
*/
|
|
1467
1986
|
async deleteChart(id, options, signal) {
|
|
1468
1987
|
await deleteChart(this.client, id, options, signal);
|
|
1469
1988
|
}
|
|
1470
|
-
// Active Chart CRUD operations
|
|
1989
|
+
// Active Chart CRUD operations (Dashboard)
|
|
1990
|
+
/**
|
|
1991
|
+
* Pins a saved chart to the dashboard (Active Charts).
|
|
1992
|
+
*
|
|
1993
|
+
* Active Charts are used for building dashboards. Unlike the chart history,
|
|
1994
|
+
* active charts are meant to be displayed together with layout metadata.
|
|
1995
|
+
*
|
|
1996
|
+
* @param body - Active chart config with chart_id, order, and optional meta
|
|
1997
|
+
* @param options - Tenant, user, and scope options
|
|
1998
|
+
* @param signal - Optional AbortSignal for cancellation
|
|
1999
|
+
* @returns Created active chart entry
|
|
2000
|
+
*
|
|
2001
|
+
* @example
|
|
2002
|
+
* ```typescript
|
|
2003
|
+
* const pinned = await qp.createActiveChart({
|
|
2004
|
+
* chart_id: savedChart.id,
|
|
2005
|
+
* order: 1,
|
|
2006
|
+
* meta: { width: "full", variant: "dark" },
|
|
2007
|
+
* }, { tenantId: "tenant_123" });
|
|
2008
|
+
* ```
|
|
2009
|
+
*/
|
|
1471
2010
|
async createActiveChart(body, options, signal) {
|
|
1472
2011
|
return await createActiveChart(
|
|
1473
2012
|
this.client,
|
|
@@ -1476,6 +2015,30 @@ var QueryPanelSdkAPI = class {
|
|
|
1476
2015
|
signal
|
|
1477
2016
|
);
|
|
1478
2017
|
}
|
|
2018
|
+
/**
|
|
2019
|
+
* Lists all active charts (dashboard items) with optional live data.
|
|
2020
|
+
*
|
|
2021
|
+
* Use `withData: true` to execute each chart's SQL and include results.
|
|
2022
|
+
* This is the primary method for loading a complete dashboard.
|
|
2023
|
+
*
|
|
2024
|
+
* @param options - Filtering and data options including withData
|
|
2025
|
+
* @param signal - Optional AbortSignal for cancellation
|
|
2026
|
+
* @returns Paginated list of active charts with optional live data
|
|
2027
|
+
*
|
|
2028
|
+
* @example
|
|
2029
|
+
* ```typescript
|
|
2030
|
+
* // Load dashboard with live data
|
|
2031
|
+
* const dashboard = await qp.listActiveCharts({
|
|
2032
|
+
* tenantId: "tenant_123",
|
|
2033
|
+
* withData: true,
|
|
2034
|
+
* });
|
|
2035
|
+
*
|
|
2036
|
+
* dashboard.data.forEach(item => {
|
|
2037
|
+
* console.log(item.chart?.title);
|
|
2038
|
+
* console.log(item.chart?.vega_lite_spec.data.values);
|
|
2039
|
+
* });
|
|
2040
|
+
* ```
|
|
2041
|
+
*/
|
|
1479
2042
|
async listActiveCharts(options, signal) {
|
|
1480
2043
|
return await listActiveCharts(
|
|
1481
2044
|
this.client,
|
|
@@ -1484,6 +2047,22 @@ var QueryPanelSdkAPI = class {
|
|
|
1484
2047
|
signal
|
|
1485
2048
|
);
|
|
1486
2049
|
}
|
|
2050
|
+
/**
|
|
2051
|
+
* Retrieves a single active chart by ID.
|
|
2052
|
+
*
|
|
2053
|
+
* @param id - Active chart ID
|
|
2054
|
+
* @param options - Options including withData for live data
|
|
2055
|
+
* @param signal - Optional AbortSignal for cancellation
|
|
2056
|
+
* @returns Active chart with associated chart data
|
|
2057
|
+
*
|
|
2058
|
+
* @example
|
|
2059
|
+
* ```typescript
|
|
2060
|
+
* const activeChart = await qp.getActiveChart("active_123", {
|
|
2061
|
+
* tenantId: "tenant_123",
|
|
2062
|
+
* withData: true,
|
|
2063
|
+
* });
|
|
2064
|
+
* ```
|
|
2065
|
+
*/
|
|
1487
2066
|
async getActiveChart(id, options, signal) {
|
|
1488
2067
|
return await getActiveChart(
|
|
1489
2068
|
this.client,
|
|
@@ -1493,6 +2072,25 @@ var QueryPanelSdkAPI = class {
|
|
|
1493
2072
|
signal
|
|
1494
2073
|
);
|
|
1495
2074
|
}
|
|
2075
|
+
/**
|
|
2076
|
+
* Updates an active chart's order or metadata.
|
|
2077
|
+
*
|
|
2078
|
+
* Use this to reorder dashboard items or update layout hints.
|
|
2079
|
+
*
|
|
2080
|
+
* @param id - Active chart ID to update
|
|
2081
|
+
* @param body - Fields to update (order, meta)
|
|
2082
|
+
* @param options - Tenant, user, and scope options
|
|
2083
|
+
* @param signal - Optional AbortSignal for cancellation
|
|
2084
|
+
* @returns Updated active chart
|
|
2085
|
+
*
|
|
2086
|
+
* @example
|
|
2087
|
+
* ```typescript
|
|
2088
|
+
* const updated = await qp.updateActiveChart("active_123", {
|
|
2089
|
+
* order: 5,
|
|
2090
|
+
* meta: { width: "half" },
|
|
2091
|
+
* }, { tenantId: "tenant_123" });
|
|
2092
|
+
* ```
|
|
2093
|
+
*/
|
|
1496
2094
|
async updateActiveChart(id, body, options, signal) {
|
|
1497
2095
|
return await updateActiveChart(
|
|
1498
2096
|
this.client,
|
|
@@ -1502,6 +2100,20 @@ var QueryPanelSdkAPI = class {
|
|
|
1502
2100
|
signal
|
|
1503
2101
|
);
|
|
1504
2102
|
}
|
|
2103
|
+
/**
|
|
2104
|
+
* Removes a chart from the dashboard (unpins it).
|
|
2105
|
+
*
|
|
2106
|
+
* This only removes the active chart entry, not the underlying saved chart.
|
|
2107
|
+
*
|
|
2108
|
+
* @param id - Active chart ID to delete
|
|
2109
|
+
* @param options - Tenant, user, and scope options
|
|
2110
|
+
* @param signal - Optional AbortSignal for cancellation
|
|
2111
|
+
*
|
|
2112
|
+
* @example
|
|
2113
|
+
* ```typescript
|
|
2114
|
+
* await qp.deleteActiveChart("active_123", { tenantId: "tenant_123" });
|
|
2115
|
+
* ```
|
|
2116
|
+
*/
|
|
1505
2117
|
async deleteActiveChart(id, options, signal) {
|
|
1506
2118
|
await deleteActiveChart(this.client, id, options, signal);
|
|
1507
2119
|
}
|
|
@@ -1510,7 +2122,9 @@ var QueryPanelSdkAPI = class {
|
|
|
1510
2122
|
0 && (module.exports = {
|
|
1511
2123
|
ClickHouseAdapter,
|
|
1512
2124
|
PostgresAdapter,
|
|
2125
|
+
QueryErrorCode,
|
|
1513
2126
|
QueryPanelSdkAPI,
|
|
2127
|
+
QueryPipelineError,
|
|
1514
2128
|
anonymizeResults
|
|
1515
2129
|
});
|
|
1516
2130
|
//# sourceMappingURL=index.cjs.map
|