@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 +57 -14
- package/dist/index.cjs +57 -17
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +15 -9
- package/dist/index.d.ts +15 -9
- package/dist/index.js +55 -16
- 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,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
|
-
|
|
79
|
+
Run `syncSchema` separately — see [Schema sync](#schema-sync).
|
|
80
|
+
|
|
81
|
+
### Schema sync
|
|
84
82
|
|
|
85
|
-
|
|
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
|
-
|
|
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
|
-
|
|
92
|
-
|
|
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
|
|
141
|
+
### Restricting chart types
|
|
97
142
|
|
|
98
|
-
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`).
|
|
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?.
|
|
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:
|
|
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
|
|
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?.
|
|
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:
|
|
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
|
-
...
|
|
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
|
|
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?.
|
|
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:
|
|
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
|
-
|
|
2388
|
+
pipeline,
|
|
2365
2389
|
hasQuerypanelSessionContext
|
|
2366
2390
|
);
|
|
2367
|
-
if (
|
|
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 (
|
|
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:
|
|
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:
|
|
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:
|
|
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
|