@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 CHANGED
@@ -42,18 +42,26 @@ const createPostgresClient = () => async (sql: string, params?: unknown[]) => {
42
42
  }
43
43
  };
44
44
 
45
- qp.attachPostgres("analytics", createPostgresClient(), {
46
- description: "Primary analytics warehouse",
47
- tenantFieldName: "tenant_id",
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 = sql.matchAll(tablePattern);
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 crypto.subtle.importKey(
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 crypto.subtle.sign(
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 = crypto.randomUUID();
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 = crypto.randomUUID();
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 databaseName = queryResponse.database ?? options.database ?? queryEngine.getDefaultDatabase();
1179
- if (!databaseName) {
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
- databaseName,
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: databaseName
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 = crypto.randomUUID();
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);