@querypanel/node-sdk 1.3.0-beta.1 → 1.3.0-beta.2

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
@@ -56,7 +56,7 @@ const createPostgresClient = () => async (sql: string, params?: unknown[]) => {
56
56
  });
57
57
 
58
58
  qp.attachClickhouse(
59
- "clicks", // uniq identifier for QueryPanel
59
+ "analytics", // uniq identifier for QueryPanel
60
60
  (params) => clickhouse.query(params),
61
61
  {
62
62
  database: "analytics", // database name
@@ -65,15 +65,10 @@ qp.attachClickhouse(
65
65
  },
66
66
  );
67
67
 
68
- // Syncs schema. Skips embedding if schema hasn't changed (no drift).
69
- // Pass { forceReindex: true } to force re-embedding.
70
- await qp.syncSchema("analytics", { tenantId: "tenant_123" });
71
-
72
68
  const response = await qp.ask("Top countries by revenue", {
73
69
  tenantId: "tenant_123",
74
70
  database: "analytics",
75
71
  });
76
- // Defaults to pipeline "v2" (POST /v2/query). Pass pipeline: "v1" only for legacy.
77
72
 
78
73
  console.log(response.sql);
79
74
  console.log(response.params);
@@ -81,23 +76,71 @@ console.table(response.rows);
81
76
  console.log(response.chart.vegaLiteSpec);
82
77
  ```
83
78
 
84
- ### Custom system instructions (v2 pipeline)
79
+ Run `syncSchema` separately — see [Schema sync](#schema-sync).
80
+
81
+ ### Schema sync
85
82
 
86
- If you need to enforce tenant-specific policies that should apply to every query (for example, data retention windows), you can inject additional **system prompt** instructions into the v2 pipeline (the default):
83
+ `syncSchema` introspects an attached database and uploads table/column metadata so natural-language questions can generate accurate SQL. Treat it like a **migration**, not part of `ask()`:
84
+
85
+ - Run it once when you first connect a database.
86
+ - Run it again when tables or columns change.
87
+ - Do not call it on every request — put it in a setup/deploy script instead.
87
88
 
88
89
  ```ts
89
- const response = await qp.ask("Revenue by product", {
90
+ // scripts/sync-schema.ts — run once at setup, or after schema changes
91
+ await qp.syncSchema("pg_demo", { tenantId: "tenant_123" });
92
+ await qp.syncSchema("analytics", { tenantId: "tenant_123" });
93
+
94
+ // Unchanged schemas skip re-embedding. Force a rebuild only when needed:
95
+ await qp.syncSchema("analytics", {
90
96
  tenantId: "tenant_123",
97
+ forceReindex: true,
98
+ });
99
+ ```
100
+
101
+ Optionally limit the payload with `tables: ["orders"]`.
102
+
103
+ ### Custom system prompt (`systemPrompt`)
104
+
105
+ Pass `systemPrompt` on `ask()` to inject extra instructions for SQL generation. Use it when a **client** needs policies QueryPanel does not know about — retention windows, a max date range, excluded event types, and similar product rules. Look up that client’s config in your backend and pass it on every question.
106
+
107
+ The text is appended to the SQL generator (and reflection) prompts. When it conflicts with generic defaults (for example the usual 30-day trend window), the caller instructions win. It does **not** replace tenant isolation — still attach with `tenantFieldName` and pass `tenantId`. Max length is 8,000 characters. There is no constructor default; `modifyChart()` does not take `systemPrompt`.
108
+
109
+ ```ts
110
+ function systemPromptForClient(client: { retentionDays: number }) {
111
+ return [
112
+ `Data retention: only query rows from the last ${client.retentionDays} days.`,
113
+ `Never return a date range longer than ${client.retentionDays} days, even if the user asks for all time or a wider window.`,
114
+ "Clamp any requested range to that maximum and bind start/end dates as parameters.",
115
+ ].join(" ");
116
+ }
117
+
118
+ const client = await loadClient(req); // your auth + billing/retention settings
119
+
120
+ const response = await qp.ask("Revenue by product over all time", {
121
+ tenantId: client.tenantId,
91
122
  database: "analytics",
92
- systemPrompt: "Retention policy: only query data from the last 30 days.",
123
+ systemPrompt: systemPromptForClient(client),
93
124
  });
94
125
  ```
95
126
 
96
- > **Deprecated:** `pipeline: "v1"` still hits `POST /query` for compatibility. Prefer omitting `pipeline` or passing `"v2"`.
127
+ Other per-client instructions:
128
+
129
+ ```ts
130
+ const response = await qp.ask("Show order volume by week", {
131
+ tenantId: client.tenantId,
132
+ database: "analytics",
133
+ systemPrompt: [
134
+ "Default time range: last 90 days unless the user names a shorter window.",
135
+ "Maximum lookback: 90 days from today.",
136
+ "Exclude orders with status refunded or cancelled.",
137
+ ].join(" "),
138
+ });
139
+ ```
97
140
 
98
- ### Restricting VizSpec chart types
141
+ ### Restricting chart types
99
142
 
100
- If your UI only supports a subset of VizSpec chart kinds (for example no area or scatter), set **`supportedChartTypes`** on the SDK constructor or on each `ask` / `modifyChart` call. The API’s `/vizspec` step then steers generation toward allowed types only (`line`, `bar`, `column`, `area`, `scatter`, `pie`). This applies whenever you use **`chartType: "vizspec"`** (including the default v2 flow after SQL runs).
143
+ If your UI only supports a subset of chart kinds (for example no area or scatter), set **`supportedChartTypes`** on the SDK constructor or on each `ask` / `modifyChart` call (`line`, `bar`, `column`, `area`, `scatter`, `pie`).
101
144
 
102
145
  ```ts
103
146
  import { QueryPanelSdkAPI, ALL_VIZ_CHART_TYPES, type ChartType } from "@querypanel/sdk";
@@ -112,8 +155,6 @@ const qp = new QueryPanelSdkAPI(url, privateKey, workspaceId, {
112
155
  await qp.ask("Revenue by month", {
113
156
  tenantId: "t1",
114
157
  database: "analytics",
115
- chartType: "vizspec",
116
- pipeline: "v2",
117
158
  supportedChartTypes: allowed,
118
159
  });
119
160
  ```
package/dist/index.cjs CHANGED
@@ -1968,11 +1968,17 @@ function buildSchemaRequest(databaseName, adapter, introspection, metadata) {
1968
1968
  dialect,
1969
1969
  tables
1970
1970
  };
1971
- if (metadata?.tenantFieldName && metadata?.tenantFieldType && metadata?.enforceTenantIsolation !== void 0) {
1971
+ if (metadata?.enforceTenantIsolation === false) {
1972
+ request.tenant_settings = {
1973
+ tenantFieldName: metadata.tenantFieldName ?? "",
1974
+ tenantFieldType: metadata.tenantFieldType ?? "String",
1975
+ enforceTenantIsolation: false
1976
+ };
1977
+ } else if (metadata?.tenantFieldName && metadata?.tenantFieldType) {
1972
1978
  request.tenant_settings = {
1973
1979
  tenantFieldName: metadata.tenantFieldName,
1974
1980
  tenantFieldType: metadata.tenantFieldType,
1975
- enforceTenantIsolation: metadata.enforceTenantIsolation
1981
+ enforceTenantIsolation: true
1976
1982
  };
1977
1983
  }
1978
1984
  return request;
@@ -2000,11 +2006,17 @@ async function ask(client, queryEngine, question, options, signal) {
2000
2006
  const databaseName = options.database ?? queryEngine.getDefaultDatabase();
2001
2007
  const metadata = databaseName ? queryEngine.getDatabaseMetadata(databaseName) : void 0;
2002
2008
  let tenantSettings;
2003
- if (metadata?.tenantFieldName) {
2009
+ if (metadata?.enforceTenantIsolation === false) {
2010
+ tenantSettings = {
2011
+ tenantFieldName: metadata.tenantFieldName ?? "",
2012
+ tenantFieldType: metadata.tenantFieldType ?? "String",
2013
+ enforceTenantIsolation: false
2014
+ };
2015
+ } else if (metadata?.tenantFieldName) {
2004
2016
  tenantSettings = {
2005
2017
  tenantFieldName: metadata.tenantFieldName,
2006
2018
  tenantFieldType: metadata.tenantFieldType,
2007
- enforceTenantIsolation: metadata.enforceTenantIsolation
2019
+ enforceTenantIsolation: true
2008
2020
  };
2009
2021
  }
2010
2022
  const queryResponse = await client.postWithHeaders(
@@ -2351,11 +2363,17 @@ async function modifyChart(client, queryEngine, input, options, signal) {
2351
2363
  }
2352
2364
  const metadata = queryEngine.getDatabaseMetadata(databaseName);
2353
2365
  let tenantSettings;
2354
- if (metadata?.tenantFieldName) {
2366
+ if (metadata?.enforceTenantIsolation === false) {
2367
+ tenantSettings = {
2368
+ tenantFieldName: metadata.tenantFieldName ?? "",
2369
+ tenantFieldType: metadata.tenantFieldType ?? "String",
2370
+ enforceTenantIsolation: false
2371
+ };
2372
+ } else if (metadata?.tenantFieldName) {
2355
2373
  tenantSettings = {
2356
2374
  tenantFieldName: metadata.tenantFieldName,
2357
2375
  tenantFieldType: metadata.tenantFieldType,
2358
- enforceTenantIsolation: metadata.enforceTenantIsolation
2376
+ enforceTenantIsolation: true
2359
2377
  };
2360
2378
  }
2361
2379
  if (hasCustomSql) {
@@ -2611,6 +2629,12 @@ var ALL_VIZ_CHART_TYPES = [
2611
2629
  ];
2612
2630
 
2613
2631
  // src/index.ts
2632
+ function resolveAttachEnforceTenantIsolation(tenantFieldName, enforceTenantIsolation) {
2633
+ if (enforceTenantIsolation !== void 0) {
2634
+ return enforceTenantIsolation;
2635
+ }
2636
+ return tenantFieldName ? true : void 0;
2637
+ }
2614
2638
  var QueryPanelSdkAPI = class _QueryPanelSdkAPI {
2615
2639
  /**
2616
2640
  * @param workspaceId - Workspace UUID (same value as your org in the dashboard). Passed to the API as JWT claim `organizationId`.
@@ -2681,7 +2705,10 @@ var QueryPanelSdkAPI = class _QueryPanelSdkAPI {
2681
2705
  tags: options?.tags,
2682
2706
  tenantFieldName: options?.tenantFieldName,
2683
2707
  tenantFieldType: options?.tenantFieldType ?? "String",
2684
- enforceTenantIsolation: options?.tenantFieldName ? options?.enforceTenantIsolation ?? true : void 0
2708
+ enforceTenantIsolation: resolveAttachEnforceTenantIsolation(
2709
+ options?.tenantFieldName,
2710
+ options?.enforceTenantIsolation
2711
+ )
2685
2712
  };
2686
2713
  this.queryEngine.attachDatabase(name, adapter, metadata);
2687
2714
  }
@@ -2694,7 +2721,10 @@ var QueryPanelSdkAPI = class _QueryPanelSdkAPI {
2694
2721
  tags: options?.tags,
2695
2722
  tenantFieldName: options?.tenantFieldName,
2696
2723
  tenantFieldType: options?.tenantFieldType ?? "String",
2697
- enforceTenantIsolation: options?.tenantFieldName ? options?.enforceTenantIsolation ?? true : void 0
2724
+ enforceTenantIsolation: resolveAttachEnforceTenantIsolation(
2725
+ options?.tenantFieldName,
2726
+ options?.enforceTenantIsolation
2727
+ )
2698
2728
  };
2699
2729
  this.queryEngine.attachDatabase(name, adapter, metadata);
2700
2730
  }
@@ -2707,7 +2737,10 @@ var QueryPanelSdkAPI = class _QueryPanelSdkAPI {
2707
2737
  tags: options?.tags,
2708
2738
  tenantFieldName: options?.tenantFieldName,
2709
2739
  tenantFieldType: options?.tenantFieldType ?? "String",
2710
- enforceTenantIsolation: options?.tenantFieldName ? options?.enforceTenantIsolation ?? true : void 0
2740
+ enforceTenantIsolation: resolveAttachEnforceTenantIsolation(
2741
+ options?.tenantFieldName,
2742
+ options?.enforceTenantIsolation
2743
+ )
2711
2744
  };
2712
2745
  this.queryEngine.attachDatabase(name, adapter, metadata);
2713
2746
  }