@querypanel/node-sdk 1.3.0-beta.0 → 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,10 +65,6 @@ 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",
@@ -80,22 +76,71 @@ console.table(response.rows);
80
76
  console.log(response.chart.vegaLiteSpec);
81
77
  ```
82
78
 
83
- ### Custom system instructions (v2 pipeline)
79
+ Run `syncSchema` separately — see [Schema sync](#schema-sync).
80
+
81
+ ### Schema sync
84
82
 
85
- 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:
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.
86
88
 
87
89
  ```ts
88
- 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", {
89
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,
122
+ database: "analytics",
123
+ systemPrompt: systemPromptForClient(client),
124
+ });
125
+ ```
126
+
127
+ Other per-client instructions:
128
+
129
+ ```ts
130
+ const response = await qp.ask("Show order volume by week", {
131
+ tenantId: client.tenantId,
90
132
  database: "analytics",
91
- pipeline: "v2",
92
- systemPrompt: "Retention policy: only query data from the last 30 days.",
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(" "),
93
138
  });
94
139
  ```
95
140
 
96
- ### Restricting VizSpec chart types
141
+ ### Restricting chart types
97
142
 
98
- 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`).
99
144
 
100
145
  ```ts
101
146
  import { QueryPanelSdkAPI, ALL_VIZ_CHART_TYPES, type ChartType } from "@querypanel/sdk";
@@ -110,8 +155,6 @@ const qp = new QueryPanelSdkAPI(url, privateKey, workspaceId, {
110
155
  await qp.ask("Revenue by month", {
111
156
  tenantId: "t1",
112
157
  database: "analytics",
113
- chartType: "vizspec",
114
- pipeline: "v2",
115
158
  supportedChartTypes: allowed,
116
159
  });
117
160
  ```
package/dist/index.cjs CHANGED
@@ -40,7 +40,8 @@ __export(index_exports, {
40
40
  QueryPipelineError: () => QueryPipelineError,
41
41
  anonymizeResults: () => anonymizeResults,
42
42
  mapGeneratedParams: () => mapGeneratedParams,
43
- normalizeSqlParamsInput: () => normalizeSqlParamsInput
43
+ normalizeSqlParamsInput: () => normalizeSqlParamsInput,
44
+ resolveQueryPipeline: () => resolveQueryPipeline
44
45
  });
45
46
  module.exports = __toCommonJS(index_exports);
46
47
 
@@ -1967,11 +1968,17 @@ function buildSchemaRequest(databaseName, adapter, introspection, metadata) {
1967
1968
  dialect,
1968
1969
  tables
1969
1970
  };
1970
- 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) {
1971
1978
  request.tenant_settings = {
1972
1979
  tenantFieldName: metadata.tenantFieldName,
1973
1980
  tenantFieldType: metadata.tenantFieldType,
1974
- enforceTenantIsolation: metadata.enforceTenantIsolation
1981
+ enforceTenantIsolation: true
1975
1982
  };
1976
1983
  }
1977
1984
  return request;
@@ -1982,6 +1989,9 @@ var import_node_crypto4 = __toESM(require("crypto"), 1);
1982
1989
 
1983
1990
  // src/routes/query.ts
1984
1991
  var import_node_crypto3 = __toESM(require("crypto"), 1);
1992
+ function resolveQueryPipeline(pipeline) {
1993
+ return pipeline ?? "v2";
1994
+ }
1985
1995
  async function ask(client, queryEngine, question, options, signal) {
1986
1996
  const tenantId = resolveTenantId5(client, options.tenantId);
1987
1997
  const sessionId = import_node_crypto3.default.randomUUID();
@@ -1990,16 +2000,23 @@ async function ask(client, queryEngine, question, options, signal) {
1990
2000
  let attempt = 0;
1991
2001
  let lastError = options.lastError;
1992
2002
  let previousSql = options.previousSql;
1993
- const queryEndpoint = options.pipeline === "v2" ? "/v2/query" : "/query";
2003
+ const pipeline = resolveQueryPipeline(options.pipeline);
2004
+ const queryEndpoint = pipeline === "v2" ? "/v2/query" : "/query";
1994
2005
  while (attempt <= maxRetry) {
1995
2006
  const databaseName = options.database ?? queryEngine.getDefaultDatabase();
1996
2007
  const metadata = databaseName ? queryEngine.getDatabaseMetadata(databaseName) : void 0;
1997
2008
  let tenantSettings;
1998
- 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) {
1999
2016
  tenantSettings = {
2000
2017
  tenantFieldName: metadata.tenantFieldName,
2001
2018
  tenantFieldType: metadata.tenantFieldType,
2002
- enforceTenantIsolation: metadata.enforceTenantIsolation
2019
+ enforceTenantIsolation: true
2003
2020
  };
2004
2021
  }
2005
2022
  const queryResponse = await client.postWithHeaders(
@@ -2014,7 +2031,7 @@ async function ask(client, queryEngine, question, options, signal) {
2014
2031
  ...databaseName ? { database: databaseName } : {},
2015
2032
  ...metadata?.dialect ? { dialect: metadata.dialect } : {},
2016
2033
  ...options.model?.trim() ? { model: options.model.trim() } : {},
2017
- ...options.pipeline === "v2" && options.systemPrompt?.trim() ? { system_prompt: options.systemPrompt.trim() } : {},
2034
+ ...pipeline === "v2" && options.systemPrompt?.trim() ? { system_prompt: options.systemPrompt.trim() } : {},
2018
2035
  debug: options.debug ?? false
2019
2036
  },
2020
2037
  tenantId,
@@ -2329,7 +2346,8 @@ async function modifyChart(client, queryEngine, input, options, signal) {
2329
2346
  const hasSqlMods = !!input.sqlModifications;
2330
2347
  const hasVizMods = !!input.vizModifications;
2331
2348
  const hasCustomSql = !!input.sqlModifications?.customSql;
2332
- const queryEndpoint = options?.pipeline === "v2" ? "/v2/query" : "/query";
2349
+ const pipeline = resolveQueryPipeline(options?.pipeline);
2350
+ const queryEndpoint = pipeline === "v2" ? "/v2/query" : "/query";
2333
2351
  let finalSql = input.sql;
2334
2352
  let finalParams = input.params ?? {};
2335
2353
  let paramMetadata = [];
@@ -2345,11 +2363,17 @@ async function modifyChart(client, queryEngine, input, options, signal) {
2345
2363
  }
2346
2364
  const metadata = queryEngine.getDatabaseMetadata(databaseName);
2347
2365
  let tenantSettings;
2348
- 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) {
2349
2373
  tenantSettings = {
2350
2374
  tenantFieldName: metadata.tenantFieldName,
2351
2375
  tenantFieldType: metadata.tenantFieldType,
2352
- enforceTenantIsolation: metadata.enforceTenantIsolation
2376
+ enforceTenantIsolation: true
2353
2377
  };
2354
2378
  }
2355
2379
  if (hasCustomSql) {
@@ -2361,10 +2385,10 @@ async function modifyChart(client, queryEngine, input, options, signal) {
2361
2385
  const modifiedQuestion = buildModifiedQuestion(
2362
2386
  input.question,
2363
2387
  input.sqlModifications,
2364
- options?.pipeline,
2388
+ pipeline,
2365
2389
  hasQuerypanelSessionContext
2366
2390
  );
2367
- if (options?.pipeline === "v2") {
2391
+ if (pipeline === "v2") {
2368
2392
  finalQuestion = modifiedQuestion;
2369
2393
  }
2370
2394
  const queryResponse = await client.post(
@@ -2388,7 +2412,7 @@ async function modifyChart(client, queryEngine, input, options, signal) {
2388
2412
  finalSql = queryResponse.sql;
2389
2413
  paramMetadata = Array.isArray(queryResponse.params) ? queryResponse.params : [];
2390
2414
  finalParams = queryEngine.mapGeneratedParams(paramMetadata);
2391
- if (options?.pipeline === "v2") {
2415
+ if (pipeline === "v2") {
2392
2416
  applyDateRangeOverrides(
2393
2417
  input.sqlModifications?.dateRange,
2394
2418
  finalParams,
@@ -2605,6 +2629,12 @@ var ALL_VIZ_CHART_TYPES = [
2605
2629
  ];
2606
2630
 
2607
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
+ }
2608
2638
  var QueryPanelSdkAPI = class _QueryPanelSdkAPI {
2609
2639
  /**
2610
2640
  * @param workspaceId - Workspace UUID (same value as your org in the dashboard). Passed to the API as JWT claim `organizationId`.
@@ -2675,7 +2705,10 @@ var QueryPanelSdkAPI = class _QueryPanelSdkAPI {
2675
2705
  tags: options?.tags,
2676
2706
  tenantFieldName: options?.tenantFieldName,
2677
2707
  tenantFieldType: options?.tenantFieldType ?? "String",
2678
- enforceTenantIsolation: options?.tenantFieldName ? options?.enforceTenantIsolation ?? true : void 0
2708
+ enforceTenantIsolation: resolveAttachEnforceTenantIsolation(
2709
+ options?.tenantFieldName,
2710
+ options?.enforceTenantIsolation
2711
+ )
2679
2712
  };
2680
2713
  this.queryEngine.attachDatabase(name, adapter, metadata);
2681
2714
  }
@@ -2688,7 +2721,10 @@ var QueryPanelSdkAPI = class _QueryPanelSdkAPI {
2688
2721
  tags: options?.tags,
2689
2722
  tenantFieldName: options?.tenantFieldName,
2690
2723
  tenantFieldType: options?.tenantFieldType ?? "String",
2691
- enforceTenantIsolation: options?.tenantFieldName ? options?.enforceTenantIsolation ?? true : void 0
2724
+ enforceTenantIsolation: resolveAttachEnforceTenantIsolation(
2725
+ options?.tenantFieldName,
2726
+ options?.enforceTenantIsolation
2727
+ )
2692
2728
  };
2693
2729
  this.queryEngine.attachDatabase(name, adapter, metadata);
2694
2730
  }
@@ -2701,7 +2737,10 @@ var QueryPanelSdkAPI = class _QueryPanelSdkAPI {
2701
2737
  tags: options?.tags,
2702
2738
  tenantFieldName: options?.tenantFieldName,
2703
2739
  tenantFieldType: options?.tenantFieldType ?? "String",
2704
- enforceTenantIsolation: options?.tenantFieldName ? options?.enforceTenantIsolation ?? true : void 0
2740
+ enforceTenantIsolation: resolveAttachEnforceTenantIsolation(
2741
+ options?.tenantFieldName,
2742
+ options?.enforceTenantIsolation
2743
+ )
2705
2744
  };
2706
2745
  this.queryEngine.attachDatabase(name, adapter, metadata);
2707
2746
  }
@@ -3631,6 +3670,7 @@ var QueryPanelSdkAPI = class _QueryPanelSdkAPI {
3631
3670
  QueryPipelineError,
3632
3671
  anonymizeResults,
3633
3672
  mapGeneratedParams,
3634
- normalizeSqlParamsInput
3673
+ normalizeSqlParamsInput,
3674
+ resolveQueryPipeline
3635
3675
  });
3636
3676
  //# sourceMappingURL=index.cjs.map