@querypanel/node-sdk 1.0.44 → 1.0.45
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 +48 -0
- package/dist/index.cjs +221 -23
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +218 -8
- package/dist/index.d.ts +218 -8
- package/dist/index.js +221 -23
- package/dist/index.js.map +1 -1
- package/package.json +71 -71
package/README.md
CHANGED
|
@@ -80,6 +80,54 @@ console.table(response.rows);
|
|
|
80
80
|
console.log(response.chart.vegaLiteSpec);
|
|
81
81
|
```
|
|
82
82
|
|
|
83
|
+
## Session History & Context-Aware Queries
|
|
84
|
+
|
|
85
|
+
The SDK can link related questions into a session so follow-ups like “filter that to Europe” use prior context. The backend generates a QueryPanel session ID for every query and returns it in the response so you can reuse it.
|
|
86
|
+
|
|
87
|
+
```ts
|
|
88
|
+
const first = await qp.ask("Revenue by country", {
|
|
89
|
+
tenantId: "tenant_123",
|
|
90
|
+
database: "analytics",
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
const querypanelSessionId = first.querypanelSessionId;
|
|
94
|
+
|
|
95
|
+
const followUp = await qp.ask("Now filter that to Europe", {
|
|
96
|
+
tenantId: "tenant_123",
|
|
97
|
+
database: "analytics",
|
|
98
|
+
querypanelSessionId, // same QueryPanel session keeps context
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
console.log(followUp.sql);
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### Managing Session History
|
|
105
|
+
|
|
106
|
+
```ts
|
|
107
|
+
// List sessions
|
|
108
|
+
const sessions = await qp.listSessions({
|
|
109
|
+
tenantId: "tenant_123",
|
|
110
|
+
pagination: { page: 1, limit: 20 },
|
|
111
|
+
sortBy: "updated_at",
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
// Get a session with its turns
|
|
115
|
+
const session = await qp.getSession("session_abc123", {
|
|
116
|
+
tenantId: "tenant_123",
|
|
117
|
+
includeTurns: true,
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
// Update session title
|
|
121
|
+
await qp.updateSession(
|
|
122
|
+
"session_abc123",
|
|
123
|
+
{ title: "Q4 Revenue Analysis" },
|
|
124
|
+
{ tenantId: "tenant_123" },
|
|
125
|
+
);
|
|
126
|
+
|
|
127
|
+
// Delete a session
|
|
128
|
+
await qp.deleteSession("session_abc123", { tenantId: "tenant_123" });
|
|
129
|
+
```
|
|
130
|
+
|
|
83
131
|
## Saving & Managing Charts
|
|
84
132
|
|
|
85
133
|
The SDK allows you to save generated charts to the QueryPanel system.
|
package/dist/index.cjs
CHANGED
|
@@ -628,6 +628,22 @@ var ApiClient = class {
|
|
|
628
628
|
signal
|
|
629
629
|
});
|
|
630
630
|
}
|
|
631
|
+
async postWithHeaders(path, body, tenantId, userId, scopes, signal, sessionId) {
|
|
632
|
+
const response = await this.fetchImpl(`${this.baseUrl}${path}`, {
|
|
633
|
+
method: "POST",
|
|
634
|
+
headers: await this.buildHeaders(
|
|
635
|
+
tenantId,
|
|
636
|
+
userId,
|
|
637
|
+
scopes,
|
|
638
|
+
true,
|
|
639
|
+
sessionId
|
|
640
|
+
),
|
|
641
|
+
body: JSON.stringify(body ?? {}),
|
|
642
|
+
signal
|
|
643
|
+
});
|
|
644
|
+
const data = await this.parseResponse(response);
|
|
645
|
+
return { data, headers: response.headers };
|
|
646
|
+
}
|
|
631
647
|
async put(path, body, tenantId, userId, scopes, signal, sessionId) {
|
|
632
648
|
return await this.request(path, {
|
|
633
649
|
method: "PUT",
|
|
@@ -642,6 +658,20 @@ var ApiClient = class {
|
|
|
642
658
|
signal
|
|
643
659
|
});
|
|
644
660
|
}
|
|
661
|
+
async patch(path, body, tenantId, userId, scopes, signal, sessionId) {
|
|
662
|
+
return await this.request(path, {
|
|
663
|
+
method: "PATCH",
|
|
664
|
+
headers: await this.buildHeaders(
|
|
665
|
+
tenantId,
|
|
666
|
+
userId,
|
|
667
|
+
scopes,
|
|
668
|
+
true,
|
|
669
|
+
sessionId
|
|
670
|
+
),
|
|
671
|
+
body: JSON.stringify(body ?? {}),
|
|
672
|
+
signal
|
|
673
|
+
});
|
|
674
|
+
}
|
|
645
675
|
async delete(path, tenantId, userId, scopes, signal, sessionId) {
|
|
646
676
|
return await this.request(path, {
|
|
647
677
|
method: "DELETE",
|
|
@@ -657,6 +687,9 @@ var ApiClient = class {
|
|
|
657
687
|
}
|
|
658
688
|
async request(path, init) {
|
|
659
689
|
const response = await this.fetchImpl(`${this.baseUrl}${path}`, init);
|
|
690
|
+
return await this.parseResponse(response);
|
|
691
|
+
}
|
|
692
|
+
async parseResponse(response) {
|
|
660
693
|
const text = await response.text();
|
|
661
694
|
let json;
|
|
662
695
|
try {
|
|
@@ -1238,6 +1271,7 @@ var import_node_crypto3 = __toESM(require("crypto"), 1);
|
|
|
1238
1271
|
async function ask(client, queryEngine, question, options, signal) {
|
|
1239
1272
|
const tenantId = resolveTenantId4(client, options.tenantId);
|
|
1240
1273
|
const sessionId = import_node_crypto3.default.randomUUID();
|
|
1274
|
+
const querypanelSessionId = options.querypanelSessionId ?? sessionId;
|
|
1241
1275
|
const maxRetry = options.maxRetry ?? 0;
|
|
1242
1276
|
let attempt = 0;
|
|
1243
1277
|
let lastError = options.lastError;
|
|
@@ -1254,10 +1288,11 @@ async function ask(client, queryEngine, question, options, signal) {
|
|
|
1254
1288
|
enforceTenantIsolation: metadata.enforceTenantIsolation
|
|
1255
1289
|
};
|
|
1256
1290
|
}
|
|
1257
|
-
const queryResponse = await client.
|
|
1291
|
+
const queryResponse = await client.postWithHeaders(
|
|
1258
1292
|
"/query",
|
|
1259
1293
|
{
|
|
1260
1294
|
question,
|
|
1295
|
+
...querypanelSessionId ? { session_id: querypanelSessionId } : {},
|
|
1261
1296
|
...lastError ? { last_error: lastError } : {},
|
|
1262
1297
|
...previousSql ? { previous_sql: previousSql } : {},
|
|
1263
1298
|
...options.maxRetry ? { max_retry: options.maxRetry } : {},
|
|
@@ -1271,24 +1306,30 @@ async function ask(client, queryEngine, question, options, signal) {
|
|
|
1271
1306
|
signal,
|
|
1272
1307
|
sessionId
|
|
1273
1308
|
);
|
|
1274
|
-
|
|
1309
|
+
const responseSessionId = queryResponse.headers.get("x-querypanel-session-id") ?? querypanelSessionId;
|
|
1310
|
+
if (!queryResponse.data.success) {
|
|
1275
1311
|
throw new QueryPipelineError(
|
|
1276
|
-
queryResponse.error || "Query generation failed",
|
|
1277
|
-
queryResponse.code || "INTERNAL_ERROR",
|
|
1278
|
-
queryResponse.details
|
|
1312
|
+
queryResponse.data.error || "Query generation failed",
|
|
1313
|
+
queryResponse.data.code || "INTERNAL_ERROR",
|
|
1314
|
+
queryResponse.data.details
|
|
1279
1315
|
);
|
|
1280
1316
|
}
|
|
1281
|
-
const
|
|
1317
|
+
const sql = queryResponse.data.sql;
|
|
1318
|
+
const dialect = queryResponse.data.dialect;
|
|
1319
|
+
if (!sql || !dialect) {
|
|
1320
|
+
throw new Error("Query response missing required SQL or dialect");
|
|
1321
|
+
}
|
|
1322
|
+
const dbName = queryResponse.data.database ?? options.database ?? queryEngine.getDefaultDatabase();
|
|
1282
1323
|
if (!dbName) {
|
|
1283
1324
|
throw new Error(
|
|
1284
1325
|
"No database attached. Call attachPostgres/attachClickhouse first."
|
|
1285
1326
|
);
|
|
1286
1327
|
}
|
|
1287
|
-
const paramMetadata = Array.isArray(queryResponse.params) ? queryResponse.params : [];
|
|
1328
|
+
const paramMetadata = Array.isArray(queryResponse.data.params) ? queryResponse.data.params : [];
|
|
1288
1329
|
const paramValues = queryEngine.mapGeneratedParams(paramMetadata);
|
|
1289
1330
|
try {
|
|
1290
1331
|
const execution = await queryEngine.validateAndExecute(
|
|
1291
|
-
|
|
1332
|
+
sql,
|
|
1292
1333
|
paramValues,
|
|
1293
1334
|
dbName,
|
|
1294
1335
|
tenantId
|
|
@@ -1305,12 +1346,12 @@ async function ask(client, queryEngine, question, options, signal) {
|
|
|
1305
1346
|
"/vizspec",
|
|
1306
1347
|
{
|
|
1307
1348
|
question,
|
|
1308
|
-
sql
|
|
1309
|
-
rationale: queryResponse.rationale,
|
|
1349
|
+
sql,
|
|
1350
|
+
rationale: queryResponse.data.rationale,
|
|
1310
1351
|
fields: execution.fields,
|
|
1311
1352
|
rows: anonymizeResults(rows),
|
|
1312
1353
|
max_retries: options.chartMaxRetries ?? 3,
|
|
1313
|
-
query_id: queryResponse.queryId
|
|
1354
|
+
query_id: queryResponse.data.queryId
|
|
1314
1355
|
},
|
|
1315
1356
|
tenantId,
|
|
1316
1357
|
options.userId,
|
|
@@ -1328,12 +1369,12 @@ async function ask(client, queryEngine, question, options, signal) {
|
|
|
1328
1369
|
"/chart",
|
|
1329
1370
|
{
|
|
1330
1371
|
question,
|
|
1331
|
-
sql
|
|
1332
|
-
rationale: queryResponse.rationale,
|
|
1372
|
+
sql,
|
|
1373
|
+
rationale: queryResponse.data.rationale,
|
|
1333
1374
|
fields: execution.fields,
|
|
1334
1375
|
rows: anonymizeResults(rows),
|
|
1335
1376
|
max_retries: options.chartMaxRetries ?? 3,
|
|
1336
|
-
query_id: queryResponse.queryId
|
|
1377
|
+
query_id: queryResponse.data.queryId
|
|
1337
1378
|
},
|
|
1338
1379
|
tenantId,
|
|
1339
1380
|
options.userId,
|
|
@@ -1352,18 +1393,19 @@ async function ask(client, queryEngine, question, options, signal) {
|
|
|
1352
1393
|
}
|
|
1353
1394
|
}
|
|
1354
1395
|
return {
|
|
1355
|
-
sql
|
|
1396
|
+
sql,
|
|
1356
1397
|
params: paramValues,
|
|
1357
1398
|
paramMetadata,
|
|
1358
|
-
rationale: queryResponse.rationale,
|
|
1359
|
-
dialect
|
|
1360
|
-
queryId: queryResponse.queryId,
|
|
1399
|
+
rationale: queryResponse.data.rationale,
|
|
1400
|
+
dialect,
|
|
1401
|
+
queryId: queryResponse.data.queryId,
|
|
1361
1402
|
rows,
|
|
1362
1403
|
fields: execution.fields,
|
|
1363
1404
|
chart,
|
|
1364
|
-
context: queryResponse.context,
|
|
1405
|
+
context: queryResponse.data.context,
|
|
1365
1406
|
attempts: attempt + 1,
|
|
1366
|
-
target_db: dbName
|
|
1407
|
+
target_db: dbName,
|
|
1408
|
+
querypanelSessionId: responseSessionId ?? void 0
|
|
1367
1409
|
};
|
|
1368
1410
|
} catch (error) {
|
|
1369
1411
|
attempt++;
|
|
@@ -1371,7 +1413,7 @@ async function ask(client, queryEngine, question, options, signal) {
|
|
|
1371
1413
|
throw error;
|
|
1372
1414
|
}
|
|
1373
1415
|
lastError = error instanceof Error ? error.message : String(error);
|
|
1374
|
-
previousSql = queryResponse.sql;
|
|
1416
|
+
previousSql = queryResponse.data.sql ?? previousSql;
|
|
1375
1417
|
console.warn(
|
|
1376
1418
|
`SQL execution failed (attempt ${attempt}/${maxRetry + 1}): ${lastError}. Retrying...`
|
|
1377
1419
|
);
|
|
@@ -1606,10 +1648,79 @@ async function modifyChart(client, queryEngine, input, options, signal) {
|
|
|
1606
1648
|
};
|
|
1607
1649
|
}
|
|
1608
1650
|
|
|
1651
|
+
// src/routes/sessions.ts
|
|
1652
|
+
async function listSessions(client, options, signal) {
|
|
1653
|
+
const tenantId = resolveTenantId6(client, options?.tenantId);
|
|
1654
|
+
const params = new URLSearchParams();
|
|
1655
|
+
if (options?.pagination?.page)
|
|
1656
|
+
params.set("page", `${options.pagination.page}`);
|
|
1657
|
+
if (options?.pagination?.limit)
|
|
1658
|
+
params.set("limit", `${options.pagination.limit}`);
|
|
1659
|
+
if (options?.sortBy) params.set("sort_by", options.sortBy);
|
|
1660
|
+
if (options?.sortDir) params.set("sort_dir", options.sortDir);
|
|
1661
|
+
if (options?.title) params.set("title", options.title);
|
|
1662
|
+
if (options?.userFilter) params.set("user_id", options.userFilter);
|
|
1663
|
+
if (options?.createdFrom) params.set("created_from", options.createdFrom);
|
|
1664
|
+
if (options?.createdTo) params.set("created_to", options.createdTo);
|
|
1665
|
+
if (options?.updatedFrom) params.set("updated_from", options.updatedFrom);
|
|
1666
|
+
if (options?.updatedTo) params.set("updated_to", options.updatedTo);
|
|
1667
|
+
return await client.get(
|
|
1668
|
+
`/sessions${params.toString() ? `?${params.toString()}` : ""}`,
|
|
1669
|
+
tenantId,
|
|
1670
|
+
options?.userId,
|
|
1671
|
+
options?.scopes,
|
|
1672
|
+
signal
|
|
1673
|
+
);
|
|
1674
|
+
}
|
|
1675
|
+
async function getSession(client, sessionId, options, signal) {
|
|
1676
|
+
const tenantId = resolveTenantId6(client, options?.tenantId);
|
|
1677
|
+
const params = new URLSearchParams();
|
|
1678
|
+
if (options?.includeTurns !== void 0) {
|
|
1679
|
+
params.set("include_turns", `${options.includeTurns}`);
|
|
1680
|
+
}
|
|
1681
|
+
return await client.get(
|
|
1682
|
+
`/sessions/${encodeURIComponent(sessionId)}${params.toString() ? `?${params.toString()}` : ""}`,
|
|
1683
|
+
tenantId,
|
|
1684
|
+
options?.userId,
|
|
1685
|
+
options?.scopes,
|
|
1686
|
+
signal
|
|
1687
|
+
);
|
|
1688
|
+
}
|
|
1689
|
+
async function updateSession(client, sessionId, body, options, signal) {
|
|
1690
|
+
const tenantId = resolveTenantId6(client, options?.tenantId);
|
|
1691
|
+
return await client.patch(
|
|
1692
|
+
`/sessions/${encodeURIComponent(sessionId)}`,
|
|
1693
|
+
body,
|
|
1694
|
+
tenantId,
|
|
1695
|
+
options?.userId,
|
|
1696
|
+
options?.scopes,
|
|
1697
|
+
signal
|
|
1698
|
+
);
|
|
1699
|
+
}
|
|
1700
|
+
async function deleteSession(client, sessionId, options, signal) {
|
|
1701
|
+
const tenantId = resolveTenantId6(client, options?.tenantId);
|
|
1702
|
+
await client.delete(
|
|
1703
|
+
`/sessions/${encodeURIComponent(sessionId)}`,
|
|
1704
|
+
tenantId,
|
|
1705
|
+
options?.userId,
|
|
1706
|
+
options?.scopes,
|
|
1707
|
+
signal
|
|
1708
|
+
);
|
|
1709
|
+
}
|
|
1710
|
+
function resolveTenantId6(client, tenantId) {
|
|
1711
|
+
const resolved = tenantId ?? client.getDefaultTenantId();
|
|
1712
|
+
if (!resolved) {
|
|
1713
|
+
throw new Error(
|
|
1714
|
+
"tenantId is required. Provide it per request or via defaultTenantId option."
|
|
1715
|
+
);
|
|
1716
|
+
}
|
|
1717
|
+
return resolved;
|
|
1718
|
+
}
|
|
1719
|
+
|
|
1609
1720
|
// src/routes/vizspec.ts
|
|
1610
1721
|
var import_node_crypto5 = __toESM(require("crypto"), 1);
|
|
1611
1722
|
async function generateVizSpec(client, input, options, signal) {
|
|
1612
|
-
const tenantId =
|
|
1723
|
+
const tenantId = resolveTenantId7(client, options?.tenantId);
|
|
1613
1724
|
const sessionId = import_node_crypto5.default.randomUUID();
|
|
1614
1725
|
const response = await client.post(
|
|
1615
1726
|
"/vizspec",
|
|
@@ -1630,7 +1741,7 @@ async function generateVizSpec(client, input, options, signal) {
|
|
|
1630
1741
|
);
|
|
1631
1742
|
return response;
|
|
1632
1743
|
}
|
|
1633
|
-
function
|
|
1744
|
+
function resolveTenantId7(client, tenantId) {
|
|
1634
1745
|
const resolved = tenantId ?? client.getDefaultTenantId();
|
|
1635
1746
|
if (!resolved) {
|
|
1636
1747
|
throw new Error(
|
|
@@ -1743,6 +1854,7 @@ var QueryPanelSdkAPI = class {
|
|
|
1743
1854
|
* console.log(result.sql); // Generated SQL
|
|
1744
1855
|
* console.log(result.rows); // Query results
|
|
1745
1856
|
* console.log(result.chart); // Vega-Lite chart spec
|
|
1857
|
+
* console.log(result.querypanelSessionId); // Use for follow-ups
|
|
1746
1858
|
*
|
|
1747
1859
|
* // With automatic SQL repair on failure
|
|
1748
1860
|
* const result = await qp.ask("Show monthly trends", {
|
|
@@ -1918,6 +2030,92 @@ var QueryPanelSdkAPI = class {
|
|
|
1918
2030
|
signal
|
|
1919
2031
|
);
|
|
1920
2032
|
}
|
|
2033
|
+
// Session history CRUD operations
|
|
2034
|
+
/**
|
|
2035
|
+
* Lists query sessions with pagination and filtering.
|
|
2036
|
+
*
|
|
2037
|
+
* @param options - Filtering, pagination, and sort options
|
|
2038
|
+
* @param signal - Optional AbortSignal for cancellation
|
|
2039
|
+
* @returns Paginated list of sessions
|
|
2040
|
+
*
|
|
2041
|
+
* @example
|
|
2042
|
+
* ```typescript
|
|
2043
|
+
* const sessions = await qp.listSessions({
|
|
2044
|
+
* tenantId: "tenant_123",
|
|
2045
|
+
* pagination: { page: 1, limit: 20 },
|
|
2046
|
+
* sortBy: "updated_at",
|
|
2047
|
+
* });
|
|
2048
|
+
* ```
|
|
2049
|
+
*/
|
|
2050
|
+
async listSessions(options, signal) {
|
|
2051
|
+
return await listSessions(this.client, options, signal);
|
|
2052
|
+
}
|
|
2053
|
+
/**
|
|
2054
|
+
* Retrieves a session by session_id with optional turn history.
|
|
2055
|
+
*
|
|
2056
|
+
* @param sessionId - QueryPanel session identifier used in ask()
|
|
2057
|
+
* @param options - Tenant, user, scopes, and includeTurns flag
|
|
2058
|
+
* @param signal - Optional AbortSignal for cancellation
|
|
2059
|
+
* @returns Session metadata with optional turns
|
|
2060
|
+
*
|
|
2061
|
+
* @example
|
|
2062
|
+
* ```typescript
|
|
2063
|
+
* const session = await qp.getSession("session_123", {
|
|
2064
|
+
* tenantId: "tenant_123",
|
|
2065
|
+
* includeTurns: true,
|
|
2066
|
+
* });
|
|
2067
|
+
* ```
|
|
2068
|
+
*/
|
|
2069
|
+
async getSession(sessionId, options, signal) {
|
|
2070
|
+
return await getSession(
|
|
2071
|
+
this.client,
|
|
2072
|
+
sessionId,
|
|
2073
|
+
options,
|
|
2074
|
+
signal
|
|
2075
|
+
);
|
|
2076
|
+
}
|
|
2077
|
+
/**
|
|
2078
|
+
* Updates session metadata (title).
|
|
2079
|
+
*
|
|
2080
|
+
* @param sessionId - QueryPanel session identifier to update
|
|
2081
|
+
* @param body - Fields to update
|
|
2082
|
+
* @param options - Tenant, user, and scope options
|
|
2083
|
+
* @param signal - Optional AbortSignal for cancellation
|
|
2084
|
+
* @returns Updated session
|
|
2085
|
+
*
|
|
2086
|
+
* @example
|
|
2087
|
+
* ```typescript
|
|
2088
|
+
* const updated = await qp.updateSession(
|
|
2089
|
+
* "session_123",
|
|
2090
|
+
* { title: "Q4 Revenue Analysis" },
|
|
2091
|
+
* { tenantId: "tenant_123" },
|
|
2092
|
+
* );
|
|
2093
|
+
* ```
|
|
2094
|
+
*/
|
|
2095
|
+
async updateSession(sessionId, body, options, signal) {
|
|
2096
|
+
return await updateSession(
|
|
2097
|
+
this.client,
|
|
2098
|
+
sessionId,
|
|
2099
|
+
body,
|
|
2100
|
+
options,
|
|
2101
|
+
signal
|
|
2102
|
+
);
|
|
2103
|
+
}
|
|
2104
|
+
/**
|
|
2105
|
+
* Deletes a session and its turn history.
|
|
2106
|
+
*
|
|
2107
|
+
* @param sessionId - QueryPanel session identifier to delete
|
|
2108
|
+
* @param options - Tenant, user, and scope options
|
|
2109
|
+
* @param signal - Optional AbortSignal for cancellation
|
|
2110
|
+
*
|
|
2111
|
+
* @example
|
|
2112
|
+
* ```typescript
|
|
2113
|
+
* await qp.deleteSession("session_123", { tenantId: "tenant_123" });
|
|
2114
|
+
* ```
|
|
2115
|
+
*/
|
|
2116
|
+
async deleteSession(sessionId, options, signal) {
|
|
2117
|
+
await deleteSession(this.client, sessionId, options, signal);
|
|
2118
|
+
}
|
|
1921
2119
|
/**
|
|
1922
2120
|
* Retrieves a single chart by ID with live data.
|
|
1923
2121
|
*
|