@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 +56 -15
- package/dist/index.cjs +42 -9
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +42 -9
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -56,7 +56,7 @@ const createPostgresClient = () => async (sql: string, params?: unknown[]) => {
|
|
|
56
56
|
});
|
|
57
57
|
|
|
58
58
|
qp.attachClickhouse(
|
|
59
|
-
"
|
|
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
|
-
|
|
79
|
+
Run `syncSchema` separately — see [Schema sync](#schema-sync).
|
|
80
|
+
|
|
81
|
+
### Schema sync
|
|
85
82
|
|
|
86
|
-
|
|
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
|
-
|
|
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:
|
|
123
|
+
systemPrompt: systemPromptForClient(client),
|
|
93
124
|
});
|
|
94
125
|
```
|
|
95
126
|
|
|
96
|
-
|
|
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
|
|
141
|
+
### Restricting chart types
|
|
99
142
|
|
|
100
|
-
If your UI only supports a subset of
|
|
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?.
|
|
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:
|
|
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?.
|
|
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:
|
|
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?.
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
2740
|
+
enforceTenantIsolation: resolveAttachEnforceTenantIsolation(
|
|
2741
|
+
options?.tenantFieldName,
|
|
2742
|
+
options?.enforceTenantIsolation
|
|
2743
|
+
)
|
|
2711
2744
|
};
|
|
2712
2745
|
this.queryEngine.attachDatabase(name, adapter, metadata);
|
|
2713
2746
|
}
|