@querypanel/node-sdk 1.0.43 → 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/dist/index.js CHANGED
@@ -587,6 +587,22 @@ var ApiClient = class {
587
587
  signal
588
588
  });
589
589
  }
590
+ async postWithHeaders(path, body, tenantId, userId, scopes, signal, sessionId) {
591
+ const response = await this.fetchImpl(`${this.baseUrl}${path}`, {
592
+ method: "POST",
593
+ headers: await this.buildHeaders(
594
+ tenantId,
595
+ userId,
596
+ scopes,
597
+ true,
598
+ sessionId
599
+ ),
600
+ body: JSON.stringify(body ?? {}),
601
+ signal
602
+ });
603
+ const data = await this.parseResponse(response);
604
+ return { data, headers: response.headers };
605
+ }
590
606
  async put(path, body, tenantId, userId, scopes, signal, sessionId) {
591
607
  return await this.request(path, {
592
608
  method: "PUT",
@@ -601,6 +617,20 @@ var ApiClient = class {
601
617
  signal
602
618
  });
603
619
  }
620
+ async patch(path, body, tenantId, userId, scopes, signal, sessionId) {
621
+ return await this.request(path, {
622
+ method: "PATCH",
623
+ headers: await this.buildHeaders(
624
+ tenantId,
625
+ userId,
626
+ scopes,
627
+ true,
628
+ sessionId
629
+ ),
630
+ body: JSON.stringify(body ?? {}),
631
+ signal
632
+ });
633
+ }
604
634
  async delete(path, tenantId, userId, scopes, signal, sessionId) {
605
635
  return await this.request(path, {
606
636
  method: "DELETE",
@@ -616,6 +646,9 @@ var ApiClient = class {
616
646
  }
617
647
  async request(path, init) {
618
648
  const response = await this.fetchImpl(`${this.baseUrl}${path}`, init);
649
+ return await this.parseResponse(response);
650
+ }
651
+ async parseResponse(response) {
619
652
  const text = await response.text();
620
653
  let json;
621
654
  try {
@@ -842,6 +875,57 @@ var QueryEngine = class {
842
875
  }
843
876
  };
844
877
 
878
+ // src/errors.ts
879
+ var QueryErrorCode = {
880
+ // Moderation errors
881
+ MODERATION_FAILED: "MODERATION_FAILED",
882
+ // Guardrail errors
883
+ RELEVANCE_CHECK_FAILED: "RELEVANCE_CHECK_FAILED",
884
+ SECURITY_CHECK_FAILED: "SECURITY_CHECK_FAILED",
885
+ // SQL generation errors
886
+ SQL_GENERATION_FAILED: "SQL_GENERATION_FAILED",
887
+ // SQL validation errors
888
+ SQL_VALIDATION_FAILED: "SQL_VALIDATION_FAILED",
889
+ // Context retrieval errors
890
+ CONTEXT_RETRIEVAL_FAILED: "CONTEXT_RETRIEVAL_FAILED",
891
+ // General errors
892
+ INTERNAL_ERROR: "INTERNAL_ERROR",
893
+ AUTHENTICATION_REQUIRED: "AUTHENTICATION_REQUIRED",
894
+ VALIDATION_ERROR: "VALIDATION_ERROR"
895
+ };
896
+ var QueryPipelineError = class extends Error {
897
+ constructor(message, code, details) {
898
+ super(message);
899
+ this.code = code;
900
+ this.details = details;
901
+ this.name = "QueryPipelineError";
902
+ }
903
+ /**
904
+ * Check if this is a moderation error
905
+ */
906
+ isModeration() {
907
+ return this.code === QueryErrorCode.MODERATION_FAILED;
908
+ }
909
+ /**
910
+ * Check if this is a relevance error (question not related to database)
911
+ */
912
+ isRelevanceError() {
913
+ return this.code === QueryErrorCode.RELEVANCE_CHECK_FAILED;
914
+ }
915
+ /**
916
+ * Check if this is a security error (SQL injection, prompt injection, etc.)
917
+ */
918
+ isSecurityError() {
919
+ return this.code === QueryErrorCode.SECURITY_CHECK_FAILED;
920
+ }
921
+ /**
922
+ * Check if this is any guardrail error (relevance or security)
923
+ */
924
+ isGuardrailError() {
925
+ return this.isRelevanceError() || this.isSecurityError();
926
+ }
927
+ };
928
+
845
929
  // src/routes/charts.ts
846
930
  async function createChart(client, body, options, signal) {
847
931
  const tenantId = resolveTenantId(client, options?.tenantId);
@@ -878,15 +962,10 @@ async function listCharts(client, queryEngine, options, signal) {
878
962
  );
879
963
  if (options?.includeData) {
880
964
  response.data = await Promise.all(
881
- response.data.map(async (chart) => ({
882
- ...chart,
883
- vega_lite_spec: {
884
- ...chart.vega_lite_spec,
885
- data: {
886
- values: await executeChartQuery(queryEngine, chart, tenantId)
887
- }
888
- }
889
- }))
965
+ response.data.map(async (chart) => {
966
+ const rows = await executeChartQuery(queryEngine, chart, tenantId);
967
+ return hydrateChartWithData(chart, rows);
968
+ })
890
969
  );
891
970
  }
892
971
  return response;
@@ -900,15 +979,8 @@ async function getChart(client, queryEngine, id, options, signal) {
900
979
  options?.scopes,
901
980
  signal
902
981
  );
903
- return {
904
- ...chart,
905
- vega_lite_spec: {
906
- ...chart.vega_lite_spec,
907
- data: {
908
- values: await executeChartQuery(queryEngine, chart, tenantId)
909
- }
910
- }
911
- };
982
+ const rows = await executeChartQuery(queryEngine, chart, tenantId);
983
+ return hydrateChartWithData(chart, rows);
912
984
  }
913
985
  async function updateChart(client, id, body, options, signal) {
914
986
  const tenantId = resolveTenantId(client, options?.tenantId);
@@ -959,6 +1031,31 @@ async function executeChartQuery(queryEngine, chart, tenantId) {
959
1031
  return [];
960
1032
  }
961
1033
  }
1034
+ function hydrateChartWithData(chart, rows) {
1035
+ const spec = chart.vega_lite_spec;
1036
+ if (chart.spec_type === "vizspec") {
1037
+ const existingData = spec.data ?? {};
1038
+ return {
1039
+ ...chart,
1040
+ vega_lite_spec: {
1041
+ ...spec,
1042
+ data: {
1043
+ ...existingData,
1044
+ values: rows
1045
+ }
1046
+ }
1047
+ };
1048
+ }
1049
+ return {
1050
+ ...chart,
1051
+ vega_lite_spec: {
1052
+ ...spec,
1053
+ data: {
1054
+ values: rows
1055
+ }
1056
+ }
1057
+ };
1058
+ }
962
1059
 
963
1060
  // src/routes/active-charts.ts
964
1061
  async function createActiveChart(client, body, options, signal) {
@@ -1133,6 +1230,7 @@ import crypto3 from "crypto";
1133
1230
  async function ask(client, queryEngine, question, options, signal) {
1134
1231
  const tenantId = resolveTenantId4(client, options.tenantId);
1135
1232
  const sessionId = crypto3.randomUUID();
1233
+ const querypanelSessionId = options.querypanelSessionId ?? sessionId;
1136
1234
  const maxRetry = options.maxRetry ?? 0;
1137
1235
  let attempt = 0;
1138
1236
  let lastError = options.lastError;
@@ -1149,10 +1247,11 @@ async function ask(client, queryEngine, question, options, signal) {
1149
1247
  enforceTenantIsolation: metadata.enforceTenantIsolation
1150
1248
  };
1151
1249
  }
1152
- const queryResponse = await client.post(
1250
+ const queryResponse = await client.postWithHeaders(
1153
1251
  "/query",
1154
1252
  {
1155
1253
  question,
1254
+ ...querypanelSessionId ? { session_id: querypanelSessionId } : {},
1156
1255
  ...lastError ? { last_error: lastError } : {},
1157
1256
  ...previousSql ? { previous_sql: previousSql } : {},
1158
1257
  ...options.maxRetry ? { max_retry: options.maxRetry } : {},
@@ -1166,17 +1265,30 @@ async function ask(client, queryEngine, question, options, signal) {
1166
1265
  signal,
1167
1266
  sessionId
1168
1267
  );
1169
- const dbName = queryResponse.database ?? options.database ?? queryEngine.getDefaultDatabase();
1268
+ const responseSessionId = queryResponse.headers.get("x-querypanel-session-id") ?? querypanelSessionId;
1269
+ if (!queryResponse.data.success) {
1270
+ throw new QueryPipelineError(
1271
+ queryResponse.data.error || "Query generation failed",
1272
+ queryResponse.data.code || "INTERNAL_ERROR",
1273
+ queryResponse.data.details
1274
+ );
1275
+ }
1276
+ const sql = queryResponse.data.sql;
1277
+ const dialect = queryResponse.data.dialect;
1278
+ if (!sql || !dialect) {
1279
+ throw new Error("Query response missing required SQL or dialect");
1280
+ }
1281
+ const dbName = queryResponse.data.database ?? options.database ?? queryEngine.getDefaultDatabase();
1170
1282
  if (!dbName) {
1171
1283
  throw new Error(
1172
1284
  "No database attached. Call attachPostgres/attachClickhouse first."
1173
1285
  );
1174
1286
  }
1175
- const paramMetadata = Array.isArray(queryResponse.params) ? queryResponse.params : [];
1287
+ const paramMetadata = Array.isArray(queryResponse.data.params) ? queryResponse.data.params : [];
1176
1288
  const paramValues = queryEngine.mapGeneratedParams(paramMetadata);
1177
1289
  try {
1178
1290
  const execution = await queryEngine.validateAndExecute(
1179
- queryResponse.sql,
1291
+ sql,
1180
1292
  paramValues,
1181
1293
  dbName,
1182
1294
  tenantId
@@ -1193,12 +1305,12 @@ async function ask(client, queryEngine, question, options, signal) {
1193
1305
  "/vizspec",
1194
1306
  {
1195
1307
  question,
1196
- sql: queryResponse.sql,
1197
- rationale: queryResponse.rationale,
1308
+ sql,
1309
+ rationale: queryResponse.data.rationale,
1198
1310
  fields: execution.fields,
1199
1311
  rows: anonymizeResults(rows),
1200
1312
  max_retries: options.chartMaxRetries ?? 3,
1201
- query_id: queryResponse.queryId
1313
+ query_id: queryResponse.data.queryId
1202
1314
  },
1203
1315
  tenantId,
1204
1316
  options.userId,
@@ -1216,12 +1328,12 @@ async function ask(client, queryEngine, question, options, signal) {
1216
1328
  "/chart",
1217
1329
  {
1218
1330
  question,
1219
- sql: queryResponse.sql,
1220
- rationale: queryResponse.rationale,
1331
+ sql,
1332
+ rationale: queryResponse.data.rationale,
1221
1333
  fields: execution.fields,
1222
1334
  rows: anonymizeResults(rows),
1223
1335
  max_retries: options.chartMaxRetries ?? 3,
1224
- query_id: queryResponse.queryId
1336
+ query_id: queryResponse.data.queryId
1225
1337
  },
1226
1338
  tenantId,
1227
1339
  options.userId,
@@ -1240,18 +1352,19 @@ async function ask(client, queryEngine, question, options, signal) {
1240
1352
  }
1241
1353
  }
1242
1354
  return {
1243
- sql: queryResponse.sql,
1355
+ sql,
1244
1356
  params: paramValues,
1245
1357
  paramMetadata,
1246
- rationale: queryResponse.rationale,
1247
- dialect: queryResponse.dialect,
1248
- queryId: queryResponse.queryId,
1358
+ rationale: queryResponse.data.rationale,
1359
+ dialect,
1360
+ queryId: queryResponse.data.queryId,
1249
1361
  rows,
1250
1362
  fields: execution.fields,
1251
1363
  chart,
1252
- context: queryResponse.context,
1364
+ context: queryResponse.data.context,
1253
1365
  attempts: attempt + 1,
1254
- target_db: dbName
1366
+ target_db: dbName,
1367
+ querypanelSessionId: responseSessionId ?? void 0
1255
1368
  };
1256
1369
  } catch (error) {
1257
1370
  attempt++;
@@ -1259,7 +1372,7 @@ async function ask(client, queryEngine, question, options, signal) {
1259
1372
  throw error;
1260
1373
  }
1261
1374
  lastError = error instanceof Error ? error.message : String(error);
1262
- previousSql = queryResponse.sql;
1375
+ previousSql = queryResponse.data.sql ?? previousSql;
1263
1376
  console.warn(
1264
1377
  `SQL execution failed (attempt ${attempt}/${maxRetry + 1}): ${lastError}. Retrying...`
1265
1378
  );
@@ -1494,10 +1607,79 @@ async function modifyChart(client, queryEngine, input, options, signal) {
1494
1607
  };
1495
1608
  }
1496
1609
 
1610
+ // src/routes/sessions.ts
1611
+ async function listSessions(client, options, signal) {
1612
+ const tenantId = resolveTenantId6(client, options?.tenantId);
1613
+ const params = new URLSearchParams();
1614
+ if (options?.pagination?.page)
1615
+ params.set("page", `${options.pagination.page}`);
1616
+ if (options?.pagination?.limit)
1617
+ params.set("limit", `${options.pagination.limit}`);
1618
+ if (options?.sortBy) params.set("sort_by", options.sortBy);
1619
+ if (options?.sortDir) params.set("sort_dir", options.sortDir);
1620
+ if (options?.title) params.set("title", options.title);
1621
+ if (options?.userFilter) params.set("user_id", options.userFilter);
1622
+ if (options?.createdFrom) params.set("created_from", options.createdFrom);
1623
+ if (options?.createdTo) params.set("created_to", options.createdTo);
1624
+ if (options?.updatedFrom) params.set("updated_from", options.updatedFrom);
1625
+ if (options?.updatedTo) params.set("updated_to", options.updatedTo);
1626
+ return await client.get(
1627
+ `/sessions${params.toString() ? `?${params.toString()}` : ""}`,
1628
+ tenantId,
1629
+ options?.userId,
1630
+ options?.scopes,
1631
+ signal
1632
+ );
1633
+ }
1634
+ async function getSession(client, sessionId, options, signal) {
1635
+ const tenantId = resolveTenantId6(client, options?.tenantId);
1636
+ const params = new URLSearchParams();
1637
+ if (options?.includeTurns !== void 0) {
1638
+ params.set("include_turns", `${options.includeTurns}`);
1639
+ }
1640
+ return await client.get(
1641
+ `/sessions/${encodeURIComponent(sessionId)}${params.toString() ? `?${params.toString()}` : ""}`,
1642
+ tenantId,
1643
+ options?.userId,
1644
+ options?.scopes,
1645
+ signal
1646
+ );
1647
+ }
1648
+ async function updateSession(client, sessionId, body, options, signal) {
1649
+ const tenantId = resolveTenantId6(client, options?.tenantId);
1650
+ return await client.patch(
1651
+ `/sessions/${encodeURIComponent(sessionId)}`,
1652
+ body,
1653
+ tenantId,
1654
+ options?.userId,
1655
+ options?.scopes,
1656
+ signal
1657
+ );
1658
+ }
1659
+ async function deleteSession(client, sessionId, options, signal) {
1660
+ const tenantId = resolveTenantId6(client, options?.tenantId);
1661
+ await client.delete(
1662
+ `/sessions/${encodeURIComponent(sessionId)}`,
1663
+ tenantId,
1664
+ options?.userId,
1665
+ options?.scopes,
1666
+ signal
1667
+ );
1668
+ }
1669
+ function resolveTenantId6(client, tenantId) {
1670
+ const resolved = tenantId ?? client.getDefaultTenantId();
1671
+ if (!resolved) {
1672
+ throw new Error(
1673
+ "tenantId is required. Provide it per request or via defaultTenantId option."
1674
+ );
1675
+ }
1676
+ return resolved;
1677
+ }
1678
+
1497
1679
  // src/routes/vizspec.ts
1498
1680
  import crypto5 from "crypto";
1499
1681
  async function generateVizSpec(client, input, options, signal) {
1500
- const tenantId = resolveTenantId6(client, options?.tenantId);
1682
+ const tenantId = resolveTenantId7(client, options?.tenantId);
1501
1683
  const sessionId = crypto5.randomUUID();
1502
1684
  const response = await client.post(
1503
1685
  "/vizspec",
@@ -1518,7 +1700,7 @@ async function generateVizSpec(client, input, options, signal) {
1518
1700
  );
1519
1701
  return response;
1520
1702
  }
1521
- function resolveTenantId6(client, tenantId) {
1703
+ function resolveTenantId7(client, tenantId) {
1522
1704
  const resolved = tenantId ?? client.getDefaultTenantId();
1523
1705
  if (!resolved) {
1524
1706
  throw new Error(
@@ -1631,6 +1813,7 @@ var QueryPanelSdkAPI = class {
1631
1813
  * console.log(result.sql); // Generated SQL
1632
1814
  * console.log(result.rows); // Query results
1633
1815
  * console.log(result.chart); // Vega-Lite chart spec
1816
+ * console.log(result.querypanelSessionId); // Use for follow-ups
1634
1817
  *
1635
1818
  * // With automatic SQL repair on failure
1636
1819
  * const result = await qp.ask("Show monthly trends", {
@@ -1806,6 +1989,92 @@ var QueryPanelSdkAPI = class {
1806
1989
  signal
1807
1990
  );
1808
1991
  }
1992
+ // Session history CRUD operations
1993
+ /**
1994
+ * Lists query sessions with pagination and filtering.
1995
+ *
1996
+ * @param options - Filtering, pagination, and sort options
1997
+ * @param signal - Optional AbortSignal for cancellation
1998
+ * @returns Paginated list of sessions
1999
+ *
2000
+ * @example
2001
+ * ```typescript
2002
+ * const sessions = await qp.listSessions({
2003
+ * tenantId: "tenant_123",
2004
+ * pagination: { page: 1, limit: 20 },
2005
+ * sortBy: "updated_at",
2006
+ * });
2007
+ * ```
2008
+ */
2009
+ async listSessions(options, signal) {
2010
+ return await listSessions(this.client, options, signal);
2011
+ }
2012
+ /**
2013
+ * Retrieves a session by session_id with optional turn history.
2014
+ *
2015
+ * @param sessionId - QueryPanel session identifier used in ask()
2016
+ * @param options - Tenant, user, scopes, and includeTurns flag
2017
+ * @param signal - Optional AbortSignal for cancellation
2018
+ * @returns Session metadata with optional turns
2019
+ *
2020
+ * @example
2021
+ * ```typescript
2022
+ * const session = await qp.getSession("session_123", {
2023
+ * tenantId: "tenant_123",
2024
+ * includeTurns: true,
2025
+ * });
2026
+ * ```
2027
+ */
2028
+ async getSession(sessionId, options, signal) {
2029
+ return await getSession(
2030
+ this.client,
2031
+ sessionId,
2032
+ options,
2033
+ signal
2034
+ );
2035
+ }
2036
+ /**
2037
+ * Updates session metadata (title).
2038
+ *
2039
+ * @param sessionId - QueryPanel session identifier to update
2040
+ * @param body - Fields to update
2041
+ * @param options - Tenant, user, and scope options
2042
+ * @param signal - Optional AbortSignal for cancellation
2043
+ * @returns Updated session
2044
+ *
2045
+ * @example
2046
+ * ```typescript
2047
+ * const updated = await qp.updateSession(
2048
+ * "session_123",
2049
+ * { title: "Q4 Revenue Analysis" },
2050
+ * { tenantId: "tenant_123" },
2051
+ * );
2052
+ * ```
2053
+ */
2054
+ async updateSession(sessionId, body, options, signal) {
2055
+ return await updateSession(
2056
+ this.client,
2057
+ sessionId,
2058
+ body,
2059
+ options,
2060
+ signal
2061
+ );
2062
+ }
2063
+ /**
2064
+ * Deletes a session and its turn history.
2065
+ *
2066
+ * @param sessionId - QueryPanel session identifier to delete
2067
+ * @param options - Tenant, user, and scope options
2068
+ * @param signal - Optional AbortSignal for cancellation
2069
+ *
2070
+ * @example
2071
+ * ```typescript
2072
+ * await qp.deleteSession("session_123", { tenantId: "tenant_123" });
2073
+ * ```
2074
+ */
2075
+ async deleteSession(sessionId, options, signal) {
2076
+ await deleteSession(this.client, sessionId, options, signal);
2077
+ }
1809
2078
  /**
1810
2079
  * Retrieves a single chart by ID with live data.
1811
2080
  *
@@ -2009,7 +2278,9 @@ var QueryPanelSdkAPI = class {
2009
2278
  export {
2010
2279
  ClickHouseAdapter,
2011
2280
  PostgresAdapter,
2281
+ QueryErrorCode,
2012
2282
  QueryPanelSdkAPI,
2283
+ QueryPipelineError,
2013
2284
  anonymizeResults
2014
2285
  };
2015
2286
  //# sourceMappingURL=index.js.map