@querypanel/node-sdk 1.0.40 → 1.0.42
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 +16 -8
- package/dist/index.cjs +40 -11
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +28 -9
- package/dist/index.js.map +1 -1
- package/package.json +71 -71
package/README.md
CHANGED
|
@@ -42,18 +42,26 @@ const createPostgresClient = () => async (sql: string, params?: unknown[]) => {
|
|
|
42
42
|
}
|
|
43
43
|
};
|
|
44
44
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
45
|
+
// Attach PostgreSQL database using the SDK's PostgresAdapter
|
|
46
|
+
// The SDK will automatically handle tenant isolation when tenantFieldName is provided
|
|
47
|
+
qp.attachPostgres(
|
|
48
|
+
"pg_demo", // a uniq identifier for QueryPanel
|
|
49
|
+
createPostgresClientFn(),
|
|
50
|
+
{
|
|
51
|
+
database: "pg_demo", // database name
|
|
52
|
+
description: "PostgreSQL demo database", // some description that QueryPanel can use
|
|
53
|
+
tenantFieldName: "tenant_id", // SDK will automatically filter by tenant_id
|
|
54
|
+
enforceTenantIsolation: true, // Ensures all queries include tenant_id filter
|
|
55
|
+
allowedTables: ["orders"], // Only sync 'orders' table - 'users' will be excluded
|
|
56
|
+
});
|
|
49
57
|
|
|
50
58
|
qp.attachClickhouse(
|
|
51
|
-
"clicks",
|
|
59
|
+
"clicks", // uniq identifier for QueryPanel
|
|
52
60
|
(params) => clickhouse.query(params),
|
|
53
61
|
{
|
|
54
|
-
database: "analytics",
|
|
55
|
-
tenantFieldName: "customer_id",
|
|
56
|
-
tenantFieldType: "String",
|
|
62
|
+
database: "analytics", // database name
|
|
63
|
+
tenantFieldName: "customer_id", // SDK will automatically filter by tenant_id
|
|
64
|
+
tenantFieldType: "String", // SDK will use it in the clickhouse query as {customer_id::String}
|
|
57
65
|
},
|
|
58
66
|
);
|
|
59
67
|
|
package/dist/index.cjs
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
2
3
|
var __defProp = Object.defineProperty;
|
|
3
4
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
5
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
5
7
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
8
|
var __export = (target, all) => {
|
|
7
9
|
for (var name in all)
|
|
@@ -15,6 +17,14 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
15
17
|
}
|
|
16
18
|
return to;
|
|
17
19
|
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
18
28
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
29
|
|
|
20
30
|
// src/index.ts
|
|
@@ -338,8 +348,9 @@ var PostgresAdapter = class {
|
|
|
338
348
|
const allowedSet = new Set(
|
|
339
349
|
this.allowedTables.map((t) => tableKey(t.schema, t.table))
|
|
340
350
|
);
|
|
351
|
+
const neutralizedSql = sql.replace(/EXTRACT\s*\([^)]*FROM\s+[^)]+\)/gi, "EXTRACT(/*neutralized*/)").replace(/SUBSTRING\s*\([^)]*FROM\s+[^)]+\)/gi, "SUBSTRING(/*neutralized*/)").replace(/TRIM\s*\([^)]*FROM\s+[^)]+\)/gi, "TRIM(/*neutralized*/)").replace(/POSITION\s*\([^)]*FROM\s+[^)]+\)/gi, "POSITION(/*neutralized*/)");
|
|
341
352
|
const tablePattern = /(?:FROM|JOIN)\s+(?:ONLY\s+)?(?:([a-zA-Z_][a-zA-Z0-9_]*)\.)?(["']?[a-zA-Z_][a-zA-Z0-9_]*["']?)/gi;
|
|
342
|
-
const matches =
|
|
353
|
+
const matches = neutralizedSql.matchAll(tablePattern);
|
|
343
354
|
for (const match of matches) {
|
|
344
355
|
const schema = match[1] ?? this.defaultSchema;
|
|
345
356
|
const table = match[2]?.replace(/['"]/g, "");
|
|
@@ -554,6 +565,7 @@ function sanitize2(value) {
|
|
|
554
565
|
}
|
|
555
566
|
|
|
556
567
|
// src/core/client.ts
|
|
568
|
+
var import_node_crypto = __toESM(require("crypto"), 1);
|
|
557
569
|
var ApiClient = class {
|
|
558
570
|
baseUrl;
|
|
559
571
|
privateKey;
|
|
@@ -711,7 +723,7 @@ var ApiClient = class {
|
|
|
711
723
|
if (this.cryptoKey) {
|
|
712
724
|
return this.cryptoKey;
|
|
713
725
|
}
|
|
714
|
-
this.cryptoKey = await
|
|
726
|
+
this.cryptoKey = await import_node_crypto.default.subtle.importKey(
|
|
715
727
|
"pkcs8",
|
|
716
728
|
this.privateKeyToArrayBuffer(this.privateKey),
|
|
717
729
|
{
|
|
@@ -753,7 +765,7 @@ var ApiClient = class {
|
|
|
753
765
|
const data = `${encodedHeader}.${encodedPayload}`;
|
|
754
766
|
const key = await this.getCryptoKey();
|
|
755
767
|
const dataBytes = new TextEncoder().encode(data);
|
|
756
|
-
const signature = await
|
|
768
|
+
const signature = await import_node_crypto.default.subtle.sign(
|
|
757
769
|
{
|
|
758
770
|
name: "RSASSA-PKCS1-v1_5"
|
|
759
771
|
},
|
|
@@ -1092,6 +1104,7 @@ function resolveTenantId2(client, tenantId) {
|
|
|
1092
1104
|
}
|
|
1093
1105
|
|
|
1094
1106
|
// src/routes/ingest.ts
|
|
1107
|
+
var import_node_crypto2 = __toESM(require("crypto"), 1);
|
|
1095
1108
|
async function syncSchema(client, queryEngine, databaseName, options, signal) {
|
|
1096
1109
|
const tenantId = resolveTenantId3(client, options.tenantId);
|
|
1097
1110
|
const adapter = queryEngine.getDatabase(databaseName);
|
|
@@ -1103,7 +1116,7 @@ async function syncSchema(client, queryEngine, databaseName, options, signal) {
|
|
|
1103
1116
|
if (options.forceReindex) {
|
|
1104
1117
|
payload.force_reindex = true;
|
|
1105
1118
|
}
|
|
1106
|
-
const sessionId =
|
|
1119
|
+
const sessionId = import_node_crypto2.default.randomUUID();
|
|
1107
1120
|
const response = await client.post(
|
|
1108
1121
|
"/ingest",
|
|
1109
1122
|
payload,
|
|
@@ -1152,22 +1165,36 @@ function buildSchemaRequest(databaseName, adapter, introspection, metadata) {
|
|
|
1152
1165
|
}
|
|
1153
1166
|
|
|
1154
1167
|
// src/routes/query.ts
|
|
1168
|
+
var import_node_crypto3 = __toESM(require("crypto"), 1);
|
|
1155
1169
|
async function ask(client, queryEngine, question, options, signal) {
|
|
1156
1170
|
const tenantId = resolveTenantId4(client, options.tenantId);
|
|
1157
|
-
const sessionId =
|
|
1171
|
+
const sessionId = import_node_crypto3.default.randomUUID();
|
|
1158
1172
|
const maxRetry = options.maxRetry ?? 0;
|
|
1159
1173
|
let attempt = 0;
|
|
1160
1174
|
let lastError = options.lastError;
|
|
1161
1175
|
let previousSql = options.previousSql;
|
|
1162
1176
|
while (attempt <= maxRetry) {
|
|
1163
1177
|
console.log({ lastError, previousSql });
|
|
1178
|
+
const databaseName = options.database ?? queryEngine.getDefaultDatabase();
|
|
1179
|
+
const metadata = databaseName ? queryEngine.getDatabaseMetadata(databaseName) : void 0;
|
|
1180
|
+
let tenantSettings;
|
|
1181
|
+
if (metadata?.tenantFieldName) {
|
|
1182
|
+
tenantSettings = {
|
|
1183
|
+
tenantFieldName: metadata.tenantFieldName,
|
|
1184
|
+
tenantFieldType: metadata.tenantFieldType,
|
|
1185
|
+
enforceTenantIsolation: metadata.enforceTenantIsolation
|
|
1186
|
+
};
|
|
1187
|
+
}
|
|
1164
1188
|
const queryResponse = await client.post(
|
|
1165
1189
|
"/query",
|
|
1166
1190
|
{
|
|
1167
1191
|
question,
|
|
1168
1192
|
...lastError ? { last_error: lastError } : {},
|
|
1169
1193
|
...previousSql ? { previous_sql: previousSql } : {},
|
|
1170
|
-
...options.maxRetry ? { max_retry: options.maxRetry } : {}
|
|
1194
|
+
...options.maxRetry ? { max_retry: options.maxRetry } : {},
|
|
1195
|
+
...tenantSettings ? { tenant_settings: tenantSettings } : {},
|
|
1196
|
+
...databaseName ? { database: databaseName } : {},
|
|
1197
|
+
...metadata?.dialect ? { dialect: metadata.dialect } : {}
|
|
1171
1198
|
},
|
|
1172
1199
|
tenantId,
|
|
1173
1200
|
options.userId,
|
|
@@ -1175,8 +1202,8 @@ async function ask(client, queryEngine, question, options, signal) {
|
|
|
1175
1202
|
signal,
|
|
1176
1203
|
sessionId
|
|
1177
1204
|
);
|
|
1178
|
-
const
|
|
1179
|
-
if (!
|
|
1205
|
+
const dbName = queryResponse.database ?? options.database ?? queryEngine.getDefaultDatabase();
|
|
1206
|
+
if (!dbName) {
|
|
1180
1207
|
throw new Error(
|
|
1181
1208
|
"No database attached. Call attachPostgres/attachClickhouse first."
|
|
1182
1209
|
);
|
|
@@ -1187,7 +1214,7 @@ async function ask(client, queryEngine, question, options, signal) {
|
|
|
1187
1214
|
const execution = await queryEngine.validateAndExecute(
|
|
1188
1215
|
queryResponse.sql,
|
|
1189
1216
|
paramValues,
|
|
1190
|
-
|
|
1217
|
+
dbName,
|
|
1191
1218
|
tenantId
|
|
1192
1219
|
);
|
|
1193
1220
|
const rows = execution.rows ?? [];
|
|
@@ -1260,7 +1287,7 @@ async function ask(client, queryEngine, question, options, signal) {
|
|
|
1260
1287
|
chart,
|
|
1261
1288
|
context: queryResponse.context,
|
|
1262
1289
|
attempts: attempt + 1,
|
|
1263
|
-
target_db:
|
|
1290
|
+
target_db: dbName
|
|
1264
1291
|
};
|
|
1265
1292
|
} catch (error) {
|
|
1266
1293
|
attempt++;
|
|
@@ -1299,9 +1326,10 @@ function anonymizeResults(rows) {
|
|
|
1299
1326
|
}
|
|
1300
1327
|
|
|
1301
1328
|
// src/routes/vizspec.ts
|
|
1329
|
+
var import_node_crypto4 = __toESM(require("crypto"), 1);
|
|
1302
1330
|
async function generateVizSpec(client, input, options, signal) {
|
|
1303
1331
|
const tenantId = resolveTenantId5(client, options?.tenantId);
|
|
1304
|
-
const sessionId =
|
|
1332
|
+
const sessionId = import_node_crypto4.default.randomUUID();
|
|
1305
1333
|
const response = await client.post(
|
|
1306
1334
|
"/vizspec",
|
|
1307
1335
|
{
|
|
@@ -1361,6 +1389,7 @@ var QueryPanelSdkAPI = class {
|
|
|
1361
1389
|
description: options?.description,
|
|
1362
1390
|
tags: options?.tags,
|
|
1363
1391
|
tenantFieldName: options?.tenantFieldName,
|
|
1392
|
+
tenantFieldType: options?.tenantFieldType ?? "String",
|
|
1364
1393
|
enforceTenantIsolation: options?.tenantFieldName ? options?.enforceTenantIsolation ?? true : void 0
|
|
1365
1394
|
};
|
|
1366
1395
|
this.queryEngine.attachDatabase(name, adapter, metadata);
|