@querypanel/node-sdk 1.0.24 → 1.0.25

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.
Files changed (137) hide show
  1. package/README.md +46 -274
  2. package/dist/index.cjs +1471 -0
  3. package/dist/index.cjs.map +1 -0
  4. package/dist/index.d.cts +468 -0
  5. package/dist/index.d.ts +468 -0
  6. package/dist/index.js +1443 -0
  7. package/dist/index.js.map +1 -0
  8. package/package.json +53 -63
  9. package/dist/cjs/adapters/clickhouse.d.ts +0 -48
  10. package/dist/cjs/adapters/clickhouse.d.ts.map +0 -1
  11. package/dist/cjs/adapters/clickhouse.js +0 -284
  12. package/dist/cjs/adapters/clickhouse.js.map +0 -1
  13. package/dist/cjs/adapters/introspection.spec.d.ts +0 -2
  14. package/dist/cjs/adapters/introspection.spec.d.ts.map +0 -1
  15. package/dist/cjs/adapters/introspection.spec.js +0 -192
  16. package/dist/cjs/adapters/introspection.spec.js.map +0 -1
  17. package/dist/cjs/adapters/postgres.d.ts +0 -46
  18. package/dist/cjs/adapters/postgres.d.ts.map +0 -1
  19. package/dist/cjs/adapters/postgres.js +0 -457
  20. package/dist/cjs/adapters/postgres.js.map +0 -1
  21. package/dist/cjs/adapters/postgres.spec.d.ts +0 -2
  22. package/dist/cjs/adapters/postgres.spec.d.ts.map +0 -1
  23. package/dist/cjs/adapters/postgres.spec.js +0 -37
  24. package/dist/cjs/adapters/postgres.spec.js.map +0 -1
  25. package/dist/cjs/adapters/types.d.ts +0 -38
  26. package/dist/cjs/adapters/types.d.ts.map +0 -1
  27. package/dist/cjs/adapters/types.js +0 -3
  28. package/dist/cjs/adapters/types.js.map +0 -1
  29. package/dist/cjs/anonymize.spec.d.ts +0 -2
  30. package/dist/cjs/anonymize.spec.d.ts.map +0 -1
  31. package/dist/cjs/anonymize.spec.js +0 -78
  32. package/dist/cjs/anonymize.spec.js.map +0 -1
  33. package/dist/cjs/clickhouseClient.spec.d.ts +0 -2
  34. package/dist/cjs/clickhouseClient.spec.d.ts.map +0 -1
  35. package/dist/cjs/clickhouseClient.spec.js +0 -286
  36. package/dist/cjs/clickhouseClient.spec.js.map +0 -1
  37. package/dist/cjs/connectors/base.d.ts +0 -14
  38. package/dist/cjs/connectors/base.d.ts.map +0 -1
  39. package/dist/cjs/connectors/base.js +0 -3
  40. package/dist/cjs/connectors/base.js.map +0 -1
  41. package/dist/cjs/connectors/clickhouse.d.ts +0 -35
  42. package/dist/cjs/connectors/clickhouse.d.ts.map +0 -1
  43. package/dist/cjs/connectors/clickhouse.js +0 -292
  44. package/dist/cjs/connectors/clickhouse.js.map +0 -1
  45. package/dist/cjs/index.d.ts +0 -498
  46. package/dist/cjs/index.d.ts.map +0 -1
  47. package/dist/cjs/index.js +0 -849
  48. package/dist/cjs/index.js.map +0 -1
  49. package/dist/cjs/index.test.d.ts +0 -2
  50. package/dist/cjs/index.test.d.ts.map +0 -1
  51. package/dist/cjs/index.test.js +0 -185
  52. package/dist/cjs/index.test.js.map +0 -1
  53. package/dist/cjs/introspectV3.d.ts +0 -45
  54. package/dist/cjs/introspectV3.d.ts.map +0 -1
  55. package/dist/cjs/introspectV3.js +0 -99
  56. package/dist/cjs/introspectV3.js.map +0 -1
  57. package/dist/cjs/multidb.spec.d.ts +0 -2
  58. package/dist/cjs/multidb.spec.d.ts.map +0 -1
  59. package/dist/cjs/multidb.spec.js +0 -76
  60. package/dist/cjs/multidb.spec.js.map +0 -1
  61. package/dist/cjs/package.json +0 -1
  62. package/dist/cjs/schema/types.d.ts +0 -73
  63. package/dist/cjs/schema/types.d.ts.map +0 -1
  64. package/dist/cjs/schema/types.js +0 -3
  65. package/dist/cjs/schema/types.js.map +0 -1
  66. package/dist/cjs/tenant-isolation.spec.d.ts +0 -2
  67. package/dist/cjs/tenant-isolation.spec.d.ts.map +0 -1
  68. package/dist/cjs/tenant-isolation.spec.js +0 -420
  69. package/dist/cjs/tenant-isolation.spec.js.map +0 -1
  70. package/dist/cjs/utils/clickhouse.d.ts +0 -9
  71. package/dist/cjs/utils/clickhouse.d.ts.map +0 -1
  72. package/dist/cjs/utils/clickhouse.js +0 -99
  73. package/dist/cjs/utils/clickhouse.js.map +0 -1
  74. package/dist/esm/adapters/clickhouse.d.ts +0 -48
  75. package/dist/esm/adapters/clickhouse.d.ts.map +0 -1
  76. package/dist/esm/adapters/clickhouse.js +0 -280
  77. package/dist/esm/adapters/clickhouse.js.map +0 -1
  78. package/dist/esm/adapters/introspection.spec.d.ts +0 -2
  79. package/dist/esm/adapters/introspection.spec.d.ts.map +0 -1
  80. package/dist/esm/adapters/introspection.spec.js +0 -190
  81. package/dist/esm/adapters/introspection.spec.js.map +0 -1
  82. package/dist/esm/adapters/postgres.d.ts +0 -46
  83. package/dist/esm/adapters/postgres.d.ts.map +0 -1
  84. package/dist/esm/adapters/postgres.js +0 -453
  85. package/dist/esm/adapters/postgres.js.map +0 -1
  86. package/dist/esm/adapters/postgres.spec.d.ts +0 -2
  87. package/dist/esm/adapters/postgres.spec.d.ts.map +0 -1
  88. package/dist/esm/adapters/postgres.spec.js +0 -35
  89. package/dist/esm/adapters/postgres.spec.js.map +0 -1
  90. package/dist/esm/adapters/types.d.ts +0 -38
  91. package/dist/esm/adapters/types.d.ts.map +0 -1
  92. package/dist/esm/adapters/types.js +0 -2
  93. package/dist/esm/adapters/types.js.map +0 -1
  94. package/dist/esm/anonymize.spec.d.ts +0 -2
  95. package/dist/esm/anonymize.spec.d.ts.map +0 -1
  96. package/dist/esm/anonymize.spec.js +0 -76
  97. package/dist/esm/anonymize.spec.js.map +0 -1
  98. package/dist/esm/clickhouseClient.spec.d.ts +0 -2
  99. package/dist/esm/clickhouseClient.spec.d.ts.map +0 -1
  100. package/dist/esm/clickhouseClient.spec.js +0 -281
  101. package/dist/esm/clickhouseClient.spec.js.map +0 -1
  102. package/dist/esm/connectors/base.d.ts +0 -14
  103. package/dist/esm/connectors/base.d.ts.map +0 -1
  104. package/dist/esm/connectors/base.js +0 -2
  105. package/dist/esm/connectors/base.js.map +0 -1
  106. package/dist/esm/connectors/clickhouse.d.ts +0 -35
  107. package/dist/esm/connectors/clickhouse.d.ts.map +0 -1
  108. package/dist/esm/connectors/clickhouse.js +0 -288
  109. package/dist/esm/connectors/clickhouse.js.map +0 -1
  110. package/dist/esm/index.d.ts +0 -498
  111. package/dist/esm/index.d.ts.map +0 -1
  112. package/dist/esm/index.js +0 -844
  113. package/dist/esm/index.js.map +0 -1
  114. package/dist/esm/index.test.d.ts +0 -2
  115. package/dist/esm/index.test.d.ts.map +0 -1
  116. package/dist/esm/index.test.js +0 -183
  117. package/dist/esm/index.test.js.map +0 -1
  118. package/dist/esm/introspectV3.d.ts +0 -45
  119. package/dist/esm/introspectV3.d.ts.map +0 -1
  120. package/dist/esm/introspectV3.js +0 -96
  121. package/dist/esm/introspectV3.js.map +0 -1
  122. package/dist/esm/multidb.spec.d.ts +0 -2
  123. package/dist/esm/multidb.spec.d.ts.map +0 -1
  124. package/dist/esm/multidb.spec.js +0 -74
  125. package/dist/esm/multidb.spec.js.map +0 -1
  126. package/dist/esm/schema/types.d.ts +0 -73
  127. package/dist/esm/schema/types.d.ts.map +0 -1
  128. package/dist/esm/schema/types.js +0 -2
  129. package/dist/esm/schema/types.js.map +0 -1
  130. package/dist/esm/tenant-isolation.spec.d.ts +0 -2
  131. package/dist/esm/tenant-isolation.spec.d.ts.map +0 -1
  132. package/dist/esm/tenant-isolation.spec.js +0 -418
  133. package/dist/esm/tenant-isolation.spec.js.map +0 -1
  134. package/dist/esm/utils/clickhouse.d.ts +0 -9
  135. package/dist/esm/utils/clickhouse.d.ts.map +0 -1
  136. package/dist/esm/utils/clickhouse.js +0 -92
  137. package/dist/esm/utils/clickhouse.js.map +0 -1
package/README.md CHANGED
@@ -1,323 +1,95 @@
1
1
  # QueryPanel Node SDK
2
2
 
3
- A lightweight Node.js client for QueryPanel's NL-to-SQL APIs. The SDK manages JWT auth, exposes helper methods for common endpoints, and provides database adapters with execution, validation, and schema introspection utilities for ClickHouse and Postgres.
3
+ A TypeScript-first client for the QueryPanel Bun/Hono API. It signs JWTs with your service private key, syncs database schemas, enforces tenant isolation, and wraps every public route under `src/routes/` (query, ingest, charts, active charts, and knowledge base).
4
4
 
5
5
  ## Installation
6
6
 
7
7
  ```bash
8
- npm install @querypanel/node-sdk
8
+ bun add @querypanel/sdk
9
+ # or
10
+ npm install @querypanel/sdk
9
11
  ```
10
12
 
11
- - Requires **Node 18+**.
12
- - `@clickhouse/client` is a peer dependency for ClickHouse integrations.
13
+ > **Runtime:** Node.js 18+ (or Bun). The SDK relies on the native `fetch` API.
13
14
 
14
- ## Quick start
15
+ ## Quickstart
15
16
 
16
17
  ```ts
17
- import { QueryPanelSdkAPI } from "@querypanel/node-sdk";
18
- import { createClient } from "@clickhouse/client";
18
+ import { QueryPanelSdkAPI } from "@querypanel/sdk";
19
+ import { Pool } from "pg";
19
20
 
20
21
  const qp = new QueryPanelSdkAPI(
21
22
  process.env.QUERYPANEL_URL!,
22
- process.env.QUERYPANEL_SERVICE_TOKEN!,
23
- process.env.PRIVATE_KEY!
24
- );
25
-
26
- // Attach a ClickHouse database (use the SDK-supplied adapter)
27
- const clickhouse = createClient({
28
- url: process.env.CLICKHOUSE_URL!,
29
- username: process.env.CLICKHOUSE_USER,
30
- password: process.env.CLICKHOUSE_PASSWORD,
31
- database: "analytics",
32
- });
33
-
34
- qp.attachClickhouse(
35
- "analytics",
36
- (params) => clickhouse.query(params),
23
+ process.env.PRIVATE_KEY!,
24
+ process.env.ORGANIZATION_ID!,
37
25
  {
38
- database: "analytics",
39
- tenantFieldName: "customer_id", // Enable tenant isolation
40
- tenantFieldType: "String", // ClickHouse type
26
+ defaultTenantId: process.env.DEFAULT_TENANT_ID,
41
27
  },
42
28
  );
43
29
 
44
- // Ask a question - tenant isolation is automatic
45
- const response = await qp.ask("Top countries by revenue", { tenantId: "tenant_123" });
46
- console.log(response.sql); // Includes WHERE customer_id = {customer_id:String}
47
- console.log(response.params); // { customer_id: "tenant_123", ... }
48
- console.table(response.rows);
49
- ```
50
-
51
- ## Authentication modes
52
-
53
- The constructor accepts a private key/organization pair for on-the-fly token generation:
54
-
55
- ```ts
56
-
57
- // provide an RSA private key and organization ID (recommended)
58
- // and save the public key on the querypanel ui
59
- const qp = new QueryPanelSdkAPI(baseUrl, privateKeyPem, organizationId);
60
- ```
61
-
62
- ## V3 Bedrock Integration
63
-
64
- The SDK provides methods to generate schema exports compatible with AWS Bedrock knowledge bases:
65
-
66
- ### introspectV3
67
-
68
- Generate a simplified schema export format suitable for Bedrock ingestion:
69
-
70
- ```ts
71
- const schema = await qp.introspectV3("analytics", {
72
- tenantId: "tenant-123",
73
- tables: ["orders", "customers"], // Optional: filter specific tables
74
- });
75
-
76
- // Output matches schema_export.json format
77
- console.log(JSON.stringify(schema, null, 2));
78
- ```
79
-
80
- ### ingestSchemaV3
81
-
82
- Introspect and ingest directly to Bedrock knowledge base:
30
+ const pool = new Pool({ connectionString: process.env.POSTGRES_URL });
83
31
 
84
- ```ts
85
- const result = await qp.ingestSchemaV3("analytics", {
86
- tenantId: "tenant-123",
87
- tables: ["orders", "customers"], // Optional
88
- });
89
-
90
- console.log(`Ingested ${result.total_documents} documents`);
91
- console.log(`Failed: ${result.failed.length}`);
92
- ```
93
-
94
- The V3 format includes:
95
- - Table overviews with column summaries and statistics
96
- - Individual column metadata documents
97
- - Foreign key relationships
98
- - Primary key indicators
99
-
100
- This data powers the Bedrock-backed `/v3/generate-sql` endpoint using Claude Haiku 4.5.
101
-
102
- ## Working with databases
103
-
104
- Adapters mediate between the SDK and your data sources. Two helpers are bundled:
105
-
106
- ### ClickHouse
107
-
108
- ```ts
109
- import {
110
- ClickHouseAdapter,
111
- type ClickHouseClientFn,
112
- } from "@querypanel/node-sdk";
113
- import { createClient } from "@clickhouse/client";
114
-
115
- const clickhouseClient = createClient({ url: "https://ch.example.com", database: "analytics" });
116
- const clickhouseFn: ClickHouseClientFn = (params) => clickhouseClient.query(params);
117
- const clickhouseAdapter = new ClickHouseAdapter(clickhouseFn, { database: "analytics" });
118
-
119
- // Optional introspection before attaching
120
- const schema = await clickhouseAdapter.introspect();
121
- console.log(schema.tables.map((t) => t.name));
122
-
123
- qp.attachDatabase("analytics", clickhouseAdapter);
124
- ```
125
-
126
- ### Postgres
127
-
128
- ```ts
129
- import { Pool } from "pg";
130
- import {
131
- PostgresAdapter,
132
- type PostgresClientFn,
133
- } from "@querypanel/node-sdk";
134
-
135
- const pool = new Pool({ connectionString: process.env.DATABASE_URL });
136
- const pgFn: PostgresClientFn = async (sql) => {
32
+ const createPostgresClient = () => async (sql: string, params?: unknown[]) => {
137
33
  const client = await pool.connect();
138
34
  try {
139
- const result = await client.query(sql);
35
+ const result = await client.query(sql, params);
140
36
  return {
141
37
  rows: result.rows,
142
- fields: result.fields.map((f) => ({ name: f.name })),
38
+ fields: result.fields.map((field) => ({ name: field.name })),
143
39
  };
144
40
  } finally {
145
41
  client.release();
146
42
  }
147
43
  };
148
44
 
149
- const pgAdapter = new PostgresAdapter(pgFn, {
150
- database: "app",
151
- defaultSchema: "public",
45
+ qp.attachPostgres("analytics", createPostgresClient(), {
46
+ description: "Primary analytics warehouse",
47
+ tenantFieldName: "tenant_id",
152
48
  });
153
49
 
154
- await pgAdapter.introspect({ tables: ["public.users"] });
155
- qp.attachDatabase("app", pgAdapter);
156
- ```
157
-
158
- Adapters expose three core methods:
159
-
160
- - `execute(sql)` – runs the query and returns `{ fields, rows }`.
161
- - `validate(sql)` – runs an `EXPLAIN` to verify syntax before execution.
162
- - `introspect(options?)` – returns structured schema metadata (`SchemaIntrospection`).
163
-
164
- ## SDK helpers
165
-
166
- Once at least one adapter is attached you can call:
167
-
168
- - `ask(question, options)` – generate SQL, validate it, execute through the adapter, and retrieve a matching Vega-Lite chart spec.
169
- - `train(payload, { tenantId, userId, scopes })` – submit curated glossary entries, metric docs, and gold SQL examples.
170
- - `stats({ tenantId, userId, scopes })` – fetch ingestion statistics.
171
- - Chart helpers: `listCharts`, `getChart`, `createChart`, `updateChart`, `deleteChart`, `listActiveCharts`, `getActiveChart`, `createActiveChart`, `updateActiveChart`, `deleteActiveChart`.
172
-
173
- Every SDK call automatically attaches the correct authentication headers and forwards optional `tenantId`, `userId`, and `scopes` when provided.
174
-
175
- ### Using V3 SQL generation
50
+ qp.attachClickhouse(
51
+ "clicks",
52
+ (params) => clickhouse.query(params),
53
+ {
54
+ database: "analytics",
55
+ tenantFieldName: "customer_id",
56
+ tenantFieldType: "String",
57
+ },
58
+ );
176
59
 
177
- By default, `ask()` uses the `/v2/generate-sql` endpoint (OpenAI + pgvector). To use the Bedrock-powered `/v3/generate-sql` endpoint (Claude Haiku 4.5), set `useV3: true`:
60
+ await qp.syncSchema("analytics", { tenantId: "tenant_123" });
178
61
 
179
- ```ts
180
62
  const response = await qp.ask("Top countries by revenue", {
181
63
  tenantId: "tenant_123",
182
- useV3: true, // Use Bedrock + Claude instead of OpenAI
183
- });
184
- ```
185
-
186
- **Important**: The `useV3` flag controls both:
187
- 1. **SQL generation endpoint**: Uses `/v3/generate-sql` instead of `/v2/generate-sql`
188
- 2. **Schema introspection**: On first use, auto-syncs schema via `/v3/ingest` (Bedrock knowledge base) instead of `/v2/vectorize-schema` (pgvector)
189
-
190
- Both endpoints support the same request/response format, making migration seamless.
191
-
192
- ### Manual sync control
193
-
194
- By default, the SDK automatically syncs your database schema on the first `ask()` call. You can disable this and sync manually:
195
-
196
- ```ts
197
- // Manual sync workflow
198
- await qp.ingestSchemaV3("analytics", {
199
- tenantId: "tenant_123",
200
- tables: ["orders", "customers"], // Optional: sync specific tables
201
- });
202
-
203
- // Now ask questions with auto-sync disabled
204
- const response = await qp.ask("Top revenue by country", {
205
- tenantId: "tenant_123",
206
- useV3: true,
207
- disableAutoSync: true, // ⚠️ Requires manual sync first
208
- });
209
- ```
210
-
211
- **Why disable auto-sync?**
212
- - 🎯 Control when schema updates happen
213
- - ⚡ Faster `ask()` calls (no sync overhead)
214
- - 💰 Reduce API calls to Bedrock
215
- - 🔧 Better for production deployments (sync on app startup, not on every request)
216
-
217
- **Recommended pattern for production:**
218
-
219
- ```ts
220
- // On app startup (once)
221
- await qp.ingestSchemaV3("analytics", { tenantId: "..." });
222
-
223
- // In request handlers (many times)
224
- app.post("/query", async (req, res) => {
225
- const result = await qp.ask(req.body.question, {
226
- tenantId: req.user.tenantId,
227
- useV3: true,
228
- disableAutoSync: true, // Already synced at startup
229
- });
230
- res.json(result);
231
- });
232
- ```
233
-
234
- ## Multi-database support
235
-
236
- The SDK keeps a registry of adapters. The first attached database becomes the default, but you can switch per request:
237
-
238
- ```ts
239
- qp.attachPostgres("users", pgFn, { database: "users" });
240
- qp.attachClickhouse("analytics", clickhouseFn);
241
-
242
- await qp.ask("Top spenders", {
243
- tenantId: "tenant_123",
244
- database: "analytics", // override default database for this request
64
+ database: "analytics",
245
65
  });
246
- ```
247
-
248
- The `available_databases` array is forwarded to the `/v2/generate-sql` endpoint so the service can pick the right dialect when it suggests queries.
249
-
250
- ## Schema introspection
251
-
252
- Both adapters can produce consistent metadata that matches `node-sdk/src/schema/types.ts`:
253
-
254
- ```ts
255
- const snapshot = await pgAdapter.introspect({ tables: ["public.orders"] });
256
- for (const table of snapshot.tables) {
257
- console.log(table.name, table.columns.length);
258
- }
259
- ```
260
66
 
261
- Use introspection to feed the training endpoint or to drive documentation for the AI planner.
262
-
263
- ## Local introspection script
264
-
265
- Test V3 introspection locally without calling the API:
266
-
267
- ```bash
268
- # Quick start
269
- npm run introspect:local
270
-
271
- # With custom database
272
- CLICKHOUSE_URL=http://localhost:8123 \
273
- CLICKHOUSE_DATABASE=analytics \
274
- TENANT_ID=my-tenant \
275
- npm run introspect:local
276
-
277
- # Filter specific tables
278
- TABLES="orders,customers" npm run introspect:local
67
+ console.log(response.sql);
68
+ console.log(response.params);
69
+ console.table(response.rows);
70
+ console.log(response.chart.vegaLiteSpec);
279
71
  ```
280
72
 
281
- This generates `schema_export.json` that you can:
282
- 1. Review locally before ingestion
283
- 2. Upload manually via `/v3/ingest`
284
- 3. Use for testing and CI/CD
285
-
286
- See `node-sdk/scripts/README.md` for full documentation.
287
-
288
- ## Testing and building
73
+ ## Building locally
289
74
 
290
75
  ```bash
291
- # run unit tests
292
- npm --prefix node-sdk test
293
-
294
- # build CommonJS and ESM artifacts
295
- npm --prefix node-sdk run build
76
+ cd node-sdk
77
+ bun install
78
+ bun run build
296
79
  ```
297
80
 
298
- ## Tenant Isolation
81
+ This runs `tsup` which emits dual ESM/CJS bundles plus type declarations to `dist/`.
299
82
 
300
- The SDK provides automatic tenant isolation enforcement to prevent data leaks in multi-tenant environments. When configured, the SDK will automatically:
83
+ ## Authentication model
301
84
 
302
- - Inject tenant parameters into all queries
303
- - ✅ Validate that generated SQL includes tenant filters
304
- - ✅ Auto-fix SQL missing tenant isolation by adding WHERE clauses
305
- - ✅ Support ClickHouse typed parameters (`{customer_id:String}`)
306
-
307
- ```typescript
308
- sdk.attachClickhouse('analytics', clickhouseFn, {
309
- tenantFieldName: 'customer_id', // Required field in your schema
310
- tenantFieldType: 'String', // ClickHouse type (String, UUID, Int64, etc.)
311
- enforceTenantIsolation: true, // Default: true (auto-fix missing filters)
312
- });
313
- ```
85
+ Every request is signed with `RS256` using the private key you pass to the constructor. The payload always includes `organizationId` and `tenantId`; `userId` and `scopes` are added when provided per call. If you still need service tokens or custom middleware, pass additional headers via the constructor.
314
86
 
315
- See [TENANT_ISOLATION.md](./TENANT_ISOLATION.md) for complete documentation.
87
+ ## Error handling
316
88
 
317
- ## Additional resources
89
+ - HTTP errors propagate as thrown `Error` instances that include `status` (and `details` when available).
90
+ - Schema ingestion failures are logged to `console.warn` during auto-sync, but you can call `syncSchema(..., { force: true })` to surface them directly.
91
+ - `ask()` raises immediately for guardrail/moderation errors because `/query` responds with 4xx/5xx.
318
92
 
319
- - `TENANT_ISOLATION.md` – Complete guide to automatic tenant isolation enforcement
320
- - `SDK-API.md` – REST endpoint reference used by the SDK
321
- - `MULTI_DATABASE.md` – Notes on attaching multiple adapters
93
+ ## Need more?
322
94
 
323
- If you hit an integration bug or need support for an additional database engine, open an issue or reach out to the QueryPanel team.
95
+ Open an issue or extend `node-sdk/src/index.ts`—every route lives in one file. Pull requests are welcome for additional adapters, richer param coercion, or convenience helpers around charts/annotations.