@querypanel/node-sdk 1.3.7 → 1.3.8

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 CHANGED
@@ -621,6 +621,20 @@ The SDK will:
621
621
 
622
622
  Without `maxRetry`, execution errors throw immediately (default behavior).
623
623
 
624
+ #### Retry when the query returns nothing
625
+
626
+ A query can run fine and still return nothing because a filter value does not match how the data stores it (`state = 'approved'` when the column holds `APPROVE`). With `retryOnEmptyResult`, the SDK treats an empty result, or a single row of only zeros/NULLs such as `COUNT(*) = 0`, like an execution error and asks for a repaired query within the same `maxRetry` budget:
627
+
628
+ ```ts
629
+ const response = await qp.ask("How many transactions were declined?", {
630
+ tenantId: "tenant_123",
631
+ maxRetry: 1,
632
+ retryOnEmptyResult: true,
633
+ });
634
+ ```
635
+
636
+ Only the fact that the result was empty and the previous SQL are sent back; no rows or values leave your backend. If the last attempt is still empty, it is returned as is (no error). Off by default; each retry is one more generation round, so expect a few extra seconds on those questions.
637
+
624
638
  ## Need more?
625
639
 
626
640
  Open an issue or extend the SDK: the public class is in `src/index.ts` and each API route has a module under `src/routes/`. Pull requests are welcome for additional adapters, richer param coercion, or convenience helpers around charts/annotations.
package/dist/index.cjs CHANGED
@@ -2492,6 +2492,15 @@ function resolveQueryPipeline(pipeline) {
2492
2492
  }
2493
2493
  return "v2";
2494
2494
  }
2495
+ var EMPTY_RESULT_ERROR = "The query ran but returned no rows (or only zero/NULL values). A filter value probably does not match how the data stores it (case, spelling, codes such as 'APPROVE' vs 'approved'), or a filter is narrower than the question. Check each filter value against the column's documented values and fix it; keep every filter the question asks for.";
2496
+ function isEmptyLikeResult(rows) {
2497
+ if (rows.length === 0) return true;
2498
+ if (rows.length > 1) return false;
2499
+ const row = rows[0];
2500
+ if (!row || typeof row !== "object") return false;
2501
+ const values = Object.values(row);
2502
+ return values.length > 0 && values.every((v) => v === null || v === 0 || v === "0" || v === 0n);
2503
+ }
2495
2504
  async function ask(client, queryEngine, question, options, signal) {
2496
2505
  const tenantId = resolveTenantId5(client, options.tenantId);
2497
2506
  const sessionId = import_node_crypto3.default.randomUUID();
@@ -2574,6 +2583,17 @@ async function ask(client, queryEngine, question, options, signal) {
2574
2583
  tenantId
2575
2584
  );
2576
2585
  const rows = execution.rows ?? [];
2586
+ if (options.retryOnEmptyResult && attempt < maxRetry && isEmptyLikeResult(rows)) {
2587
+ attempt++;
2588
+ lastError = EMPTY_RESULT_ERROR;
2589
+ previousSql = sql;
2590
+ console.warn("Query returned no data, retrying query generation", {
2591
+ attempt,
2592
+ maxAttempts: maxRetry + 1,
2593
+ sql
2594
+ });
2595
+ continue;
2596
+ }
2577
2597
  const chartType = options.chartType ?? "vega-lite";
2578
2598
  let chart = {
2579
2599
  specType: chartType,