@querypanel/node-sdk 1.2.9 → 1.3.0-beta.0

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/dist/index.cjs CHANGED
@@ -38,7 +38,9 @@ __export(index_exports, {
38
38
  QueryErrorCode: () => QueryErrorCode,
39
39
  QueryPanelSdkAPI: () => QueryPanelSdkAPI,
40
40
  QueryPipelineError: () => QueryPipelineError,
41
- anonymizeResults: () => anonymizeResults
41
+ anonymizeResults: () => anonymizeResults,
42
+ mapGeneratedParams: () => mapGeneratedParams,
43
+ normalizeSqlParamsInput: () => normalizeSqlParamsInput
42
44
  });
43
45
  module.exports = __toCommonJS(index_exports);
44
46
 
@@ -497,6 +499,37 @@ function toNumber(value) {
497
499
  }
498
500
 
499
501
  // src/adapters/postgres.ts
502
+ function maxPostgresPlaceholderIndex(sql) {
503
+ let max = 0;
504
+ for (const match of sql.matchAll(/\$(\d+)\b/g)) {
505
+ const index = Number.parseInt(match[1], 10);
506
+ if (Number.isFinite(index) && index > max) {
507
+ max = index;
508
+ }
509
+ }
510
+ return max;
511
+ }
512
+ function filterParamsForPostgresSql(sql, params) {
513
+ if (!params || Object.keys(params).length === 0) {
514
+ return void 0;
515
+ }
516
+ const placeholderCount = maxPostgresPlaceholderIndex(sql);
517
+ if (placeholderCount === 0) {
518
+ return void 0;
519
+ }
520
+ const hasNumericKeys = Object.keys(params).some((key) => /^\d+$/.test(key));
521
+ if (hasNumericKeys) {
522
+ const filtered = {};
523
+ for (let index = 1; index <= placeholderCount; index += 1) {
524
+ const key = String(index);
525
+ if (key in params) {
526
+ filtered[key] = params[key];
527
+ }
528
+ }
529
+ return Object.keys(filtered).length > 0 ? filtered : void 0;
530
+ }
531
+ return params;
532
+ }
500
533
  var PostgresAdapter = class {
501
534
  constructor(clientFn, options = {}) {
502
535
  this.clientFn = clientFn;
@@ -514,9 +547,10 @@ var PostgresAdapter = class {
514
547
  if (this.allowedTables) {
515
548
  this.validateQueryTables(sql);
516
549
  }
550
+ const boundParams = filterParamsForPostgresSql(sql, params);
517
551
  let paramArray;
518
- if (params) {
519
- paramArray = this.convertNamedToPositionalParams(params);
552
+ if (boundParams) {
553
+ paramArray = this.convertNamedToPositionalParams(sql, boundParams);
520
554
  }
521
555
  const result = await this.clientFn(sql, paramArray);
522
556
  const fields = result.fields.map((f) => f.name);
@@ -546,36 +580,43 @@ var PostgresAdapter = class {
546
580
  }
547
581
  }
548
582
  /**
549
- * Convert params to positional array for PostgreSQL.
550
- * PostgreSQL expects $1, $2, $3 in SQL and an array of values [val1, val2, val3].
551
- * When the record has numeric keys ("1", "2", "3") — e.g. from mapGeneratedParams —
552
- * use only those in order so placeholder order matches SQL. Otherwise fall back to
553
- * named keys in alphabetical order for backward compatibility.
583
+ * Convert params to positional array for PostgreSQL ($1, $2, …).
584
+ * Values must align with v2 generation order via numeric keys from mapGeneratedParams.
554
585
  */
555
- convertNamedToPositionalParams(params) {
556
- const numericKeys = Object.keys(params).filter((k) => /^\d+$/.test(k)).map((k) => Number.parseInt(k, 10)).sort((a, b) => a - b);
557
- if (numericKeys.length > 0) {
558
- const positionalParams = [];
559
- for (const key of numericKeys) {
560
- let val = params[String(key)];
561
- if (typeof val === "string") {
562
- const match = val.match(/^<([a-zA-Z0-9_]+)>$/);
563
- const namedKey = match?.[1];
564
- if (namedKey && namedKey in params) {
565
- val = params[namedKey];
566
- }
586
+ convertNamedToPositionalParams(sql, params) {
587
+ const placeholderCount = maxPostgresPlaceholderIndex(sql);
588
+ if (placeholderCount === 0) {
589
+ return [];
590
+ }
591
+ const positionalParams = [];
592
+ for (let index = 1; index <= placeholderCount; index += 1) {
593
+ const key = String(index);
594
+ let val = params[key];
595
+ if (val === void 0 && placeholderCount === 1) {
596
+ const namedKeys = Object.keys(params).filter((k) => !/^\d+$/.test(k));
597
+ if (namedKeys.length === 1) {
598
+ val = params[namedKeys[0]];
599
+ }
600
+ }
601
+ if (val === void 0) {
602
+ throw new Error(`Missing value for PostgreSQL parameter $${index}`);
603
+ }
604
+ if (typeof val === "string") {
605
+ const match = val.match(/^<([a-zA-Z0-9_]+)>$/);
606
+ const namedKey = match?.[1];
607
+ if (namedKey && namedKey in params) {
608
+ val = params[namedKey];
567
609
  }
568
- positionalParams.push(val);
569
610
  }
570
- return positionalParams;
611
+ positionalParams.push(val);
571
612
  }
572
- const namedKeys = Object.keys(params).filter((k) => !/^\d+$/.test(k)).sort();
573
- return namedKeys.map((key) => params[key]);
613
+ return positionalParams;
574
614
  }
575
615
  async validate(sql, params) {
616
+ const boundParams = filterParamsForPostgresSql(sql, params);
576
617
  let paramArray;
577
- if (params) {
578
- paramArray = this.convertNamedToPositionalParams(params);
618
+ if (boundParams) {
619
+ paramArray = this.convertNamedToPositionalParams(sql, boundParams);
579
620
  }
580
621
  await this.clientFn(`EXPLAIN ${sql}`, paramArray);
581
622
  }
@@ -749,6 +790,118 @@ function sanitize2(value) {
749
790
 
750
791
  // src/core/client.ts
751
792
  var import_node_crypto = __toESM(require("crypto"), 1);
793
+
794
+ // src/errors.ts
795
+ var QueryErrorCode = {
796
+ // Moderation errors
797
+ MODERATION_FAILED: "MODERATION_FAILED",
798
+ // Guardrail errors
799
+ RELEVANCE_CHECK_FAILED: "RELEVANCE_CHECK_FAILED",
800
+ SECURITY_CHECK_FAILED: "SECURITY_CHECK_FAILED",
801
+ // SQL generation errors
802
+ SQL_GENERATION_FAILED: "SQL_GENERATION_FAILED",
803
+ // SQL validation errors
804
+ SQL_VALIDATION_FAILED: "SQL_VALIDATION_FAILED",
805
+ // Context retrieval errors
806
+ CONTEXT_RETRIEVAL_FAILED: "CONTEXT_RETRIEVAL_FAILED",
807
+ // Clarification errors (v2)
808
+ CLARIFICATION_NEEDED: "CLARIFICATION_NEEDED",
809
+ // Bring-your-own-key / provider errors
810
+ AI_PROVIDER_AUTH_FAILED: "AI_PROVIDER_AUTH_FAILED",
811
+ AI_PROVIDER_RATE_LIMITED: "AI_PROVIDER_RATE_LIMITED",
812
+ AI_PROVIDER_UNAVAILABLE: "AI_PROVIDER_UNAVAILABLE",
813
+ // General errors
814
+ INTERNAL_ERROR: "INTERNAL_ERROR",
815
+ AUTHENTICATION_REQUIRED: "AUTHENTICATION_REQUIRED",
816
+ VALIDATION_ERROR: "VALIDATION_ERROR"
817
+ };
818
+ function isQueryErrorCode(code) {
819
+ return typeof code === "string" && Object.values(QueryErrorCode).includes(code);
820
+ }
821
+ function isAiProviderErrorCode(code) {
822
+ return code === QueryErrorCode.AI_PROVIDER_AUTH_FAILED || code === QueryErrorCode.AI_PROVIDER_RATE_LIMITED || code === QueryErrorCode.AI_PROVIDER_UNAVAILABLE;
823
+ }
824
+ function isAiProviderPipelineError(error) {
825
+ if (error instanceof QueryPipelineError) {
826
+ return isAiProviderErrorCode(error.code);
827
+ }
828
+ if (error && typeof error === "object" && "code" in error) {
829
+ return isAiProviderErrorCode(error.code);
830
+ }
831
+ return false;
832
+ }
833
+ function throwIfGenerationErrorResponse(response) {
834
+ if (!response || typeof response !== "object") {
835
+ return;
836
+ }
837
+ const record = response;
838
+ if (typeof record.error !== "string") {
839
+ return;
840
+ }
841
+ if (isQueryErrorCode(record.code)) {
842
+ throw new QueryPipelineError(record.error, record.code);
843
+ }
844
+ throw new Error(record.error);
845
+ }
846
+ var QueryPipelineError = class extends Error {
847
+ constructor(message, code, details, status) {
848
+ super(message);
849
+ this.code = code;
850
+ this.details = details;
851
+ this.status = status;
852
+ this.name = "QueryPipelineError";
853
+ }
854
+ /**
855
+ * Check if this is a moderation error
856
+ */
857
+ isModeration() {
858
+ return this.code === QueryErrorCode.MODERATION_FAILED;
859
+ }
860
+ /**
861
+ * Check if this is a relevance error (question not related to database)
862
+ */
863
+ isRelevanceError() {
864
+ return this.code === QueryErrorCode.RELEVANCE_CHECK_FAILED;
865
+ }
866
+ /**
867
+ * Check if this is a security error (SQL injection, prompt injection, etc.)
868
+ */
869
+ isSecurityError() {
870
+ return this.code === QueryErrorCode.SECURITY_CHECK_FAILED;
871
+ }
872
+ /**
873
+ * Check if this is any guardrail error (relevance or security)
874
+ */
875
+ isGuardrailError() {
876
+ return this.isRelevanceError() || this.isSecurityError();
877
+ }
878
+ /**
879
+ * Check if this is a clarification needed error (v2 pipeline)
880
+ */
881
+ isClarificationNeeded() {
882
+ return this.code === QueryErrorCode.CLARIFICATION_NEEDED;
883
+ }
884
+ /**
885
+ * Check if this is a BYOK / provider credential error
886
+ */
887
+ isAiProviderAuthError() {
888
+ return this.code === QueryErrorCode.AI_PROVIDER_AUTH_FAILED;
889
+ }
890
+ /**
891
+ * Check if this is a BYOK / provider rate-limit error
892
+ */
893
+ isAiProviderRateLimited() {
894
+ return this.code === QueryErrorCode.AI_PROVIDER_RATE_LIMITED;
895
+ }
896
+ /**
897
+ * Check if this is any BYOK / provider error (auth, rate limit, or unavailable)
898
+ */
899
+ isAiProviderError() {
900
+ return isAiProviderErrorCode(this.code);
901
+ }
902
+ };
903
+
904
+ // src/core/client.ts
752
905
  var ApiClient = class {
753
906
  constructor(baseUrl, privateKey, organizationId, options) {
754
907
  this.cryptoKey = null;
@@ -880,10 +1033,18 @@ var ApiClient = class {
880
1033
  json = void 0;
881
1034
  }
882
1035
  if (!response.ok) {
883
- const error = new Error(
884
- json?.error || response.statusText || "Request failed"
885
- );
1036
+ const message = json?.error || response.statusText || "Request failed";
1037
+ if (isQueryErrorCode(json?.code)) {
1038
+ throw new QueryPipelineError(
1039
+ message,
1040
+ json.code,
1041
+ json?.details,
1042
+ response.status
1043
+ );
1044
+ }
1045
+ const error = new Error(message);
886
1046
  error.status = response.status;
1047
+ if (json?.code) error.code = json.code;
887
1048
  if (json?.details) error.details = json.details;
888
1049
  throw error;
889
1050
  }
@@ -1098,6 +1259,45 @@ var CallbackApiClient = class {
1098
1259
  }
1099
1260
  };
1100
1261
 
1262
+ // src/core/map-generated-params.ts
1263
+ function mapGeneratedParams(params) {
1264
+ const record = {};
1265
+ params.forEach((param, index) => {
1266
+ const value = param.value;
1267
+ if (value === void 0) {
1268
+ return;
1269
+ }
1270
+ record[String(index + 1)] = value;
1271
+ const nameCandidate = typeof param.name === "string" && param.name.trim() || typeof param.placeholder === "string" && param.placeholder.trim() || typeof param.position === "number" && String(param.position) || String(index + 1);
1272
+ const key = nameCandidate.replace(/[{}:$]/g, "").trim();
1273
+ record[key] = value;
1274
+ });
1275
+ return record;
1276
+ }
1277
+ function normalizeSqlParamsInput(params) {
1278
+ if (params == null) {
1279
+ return {};
1280
+ }
1281
+ if (Array.isArray(params)) {
1282
+ if (params.length === 0) {
1283
+ return {};
1284
+ }
1285
+ const first = params[0];
1286
+ if (first && typeof first === "object" && "value" in first) {
1287
+ return mapGeneratedParams(params);
1288
+ }
1289
+ const positional = {};
1290
+ params.forEach((value, index) => {
1291
+ positional[String(index + 1)] = value;
1292
+ });
1293
+ return positional;
1294
+ }
1295
+ if (typeof params === "object") {
1296
+ return params;
1297
+ }
1298
+ return {};
1299
+ }
1300
+
1101
1301
  // src/core/query-engine.ts
1102
1302
  var QueryEngine = class {
1103
1303
  constructor() {
@@ -1168,18 +1368,7 @@ var QueryEngine = class {
1168
1368
  * - Named keys remain for tenant isolation / other lookups.
1169
1369
  */
1170
1370
  mapGeneratedParams(params) {
1171
- const record = {};
1172
- params.forEach((param, index) => {
1173
- const value = param.value;
1174
- if (value === void 0) {
1175
- return;
1176
- }
1177
- record[String(index + 1)] = value;
1178
- const nameCandidate = typeof param.name === "string" && param.name.trim() || typeof param.placeholder === "string" && param.placeholder.trim() || typeof param.position === "number" && String(param.position) || String(index + 1);
1179
- const key = nameCandidate.replace(/[{}:$]/g, "").trim();
1180
- record[key] = value;
1181
- });
1182
- return record;
1371
+ return mapGeneratedParams(params);
1183
1372
  }
1184
1373
  ensureTenantIsolation(sql, params, metadata, tenantId) {
1185
1374
  if (!metadata.tenantFieldName || metadata.enforceTenantIsolation === false) {
@@ -1199,8 +1388,10 @@ var QueryEngine = class {
1199
1388
  params[tenantField] = tenantId;
1200
1389
  tenantPredicate = `${tenantField} = @${tenantField}`;
1201
1390
  } else {
1202
- const escapedId = tenantId.replace(/'/g, "''");
1203
- tenantPredicate = `${tenantField} = '${escapedId}'`;
1391
+ const paramIndex = this.nextPostgresPlaceholderIndex(sql, params);
1392
+ params[String(paramIndex)] = tenantId;
1393
+ params[tenantField] = tenantId;
1394
+ tenantPredicate = `${tenantField} = $${paramIndex}`;
1204
1395
  }
1205
1396
  if (/\bwhere\b/i.test(sql)) {
1206
1397
  return sql.replace(
@@ -1210,64 +1401,22 @@ var QueryEngine = class {
1210
1401
  }
1211
1402
  return `${sql} WHERE ${tenantPredicate}`;
1212
1403
  }
1213
- };
1214
-
1215
- // src/errors.ts
1216
- var QueryErrorCode = {
1217
- // Moderation errors
1218
- MODERATION_FAILED: "MODERATION_FAILED",
1219
- // Guardrail errors
1220
- RELEVANCE_CHECK_FAILED: "RELEVANCE_CHECK_FAILED",
1221
- SECURITY_CHECK_FAILED: "SECURITY_CHECK_FAILED",
1222
- // SQL generation errors
1223
- SQL_GENERATION_FAILED: "SQL_GENERATION_FAILED",
1224
- // SQL validation errors
1225
- SQL_VALIDATION_FAILED: "SQL_VALIDATION_FAILED",
1226
- // Context retrieval errors
1227
- CONTEXT_RETRIEVAL_FAILED: "CONTEXT_RETRIEVAL_FAILED",
1228
- // Clarification errors (v2)
1229
- CLARIFICATION_NEEDED: "CLARIFICATION_NEEDED",
1230
- // General errors
1231
- INTERNAL_ERROR: "INTERNAL_ERROR",
1232
- AUTHENTICATION_REQUIRED: "AUTHENTICATION_REQUIRED",
1233
- VALIDATION_ERROR: "VALIDATION_ERROR"
1234
- };
1235
- var QueryPipelineError = class extends Error {
1236
- constructor(message, code, details) {
1237
- super(message);
1238
- this.code = code;
1239
- this.details = details;
1240
- this.name = "QueryPipelineError";
1241
- }
1242
- /**
1243
- * Check if this is a moderation error
1244
- */
1245
- isModeration() {
1246
- return this.code === QueryErrorCode.MODERATION_FAILED;
1247
- }
1248
- /**
1249
- * Check if this is a relevance error (question not related to database)
1250
- */
1251
- isRelevanceError() {
1252
- return this.code === QueryErrorCode.RELEVANCE_CHECK_FAILED;
1253
- }
1254
- /**
1255
- * Check if this is a security error (SQL injection, prompt injection, etc.)
1256
- */
1257
- isSecurityError() {
1258
- return this.code === QueryErrorCode.SECURITY_CHECK_FAILED;
1259
- }
1260
- /**
1261
- * Check if this is any guardrail error (relevance or security)
1262
- */
1263
- isGuardrailError() {
1264
- return this.isRelevanceError() || this.isSecurityError();
1265
- }
1266
- /**
1267
- * Check if this is a clarification needed error (v2 pipeline)
1268
- */
1269
- isClarificationNeeded() {
1270
- return this.code === QueryErrorCode.CLARIFICATION_NEEDED;
1404
+ nextPostgresPlaceholderIndex(sql, params) {
1405
+ let max = 0;
1406
+ for (const match of sql.matchAll(/\$(\d+)\b/g)) {
1407
+ const index = Number.parseInt(match[1], 10);
1408
+ if (Number.isFinite(index) && index > max) {
1409
+ max = index;
1410
+ }
1411
+ }
1412
+ if (max > 0) {
1413
+ return max + 1;
1414
+ }
1415
+ const numericKeys = Object.keys(params).filter((key) => /^\d+$/.test(key)).map((key) => Number.parseInt(key, 10)).filter((index) => Number.isFinite(index));
1416
+ if (numericKeys.length > 0) {
1417
+ return Math.max(...numericKeys) + 1;
1418
+ }
1419
+ return 1;
1271
1420
  }
1272
1421
  };
1273
1422
 
@@ -1803,6 +1952,7 @@ function resolveTenantId4(client, tenantId) {
1803
1952
  function buildSchemaRequest(databaseName, adapter, introspection, metadata) {
1804
1953
  const dialect = adapter.getDialect();
1805
1954
  const tables = introspection.tables.map((table) => ({
1955
+ schema_name: table.schema,
1806
1956
  table_name: table.name,
1807
1957
  description: table.comment ?? `Table ${table.name}`,
1808
1958
  columns: table.columns.map((column) => ({
@@ -1928,6 +2078,7 @@ async function ask(client, queryEngine, question, options, signal) {
1928
2078
  signal,
1929
2079
  sessionId
1930
2080
  );
2081
+ throwIfGenerationErrorResponse(vizspecResponse);
1931
2082
  chart = {
1932
2083
  vizSpec: vizspecResponse.spec,
1933
2084
  specType: "vizspec",
@@ -1951,6 +2102,7 @@ async function ask(client, queryEngine, question, options, signal) {
1951
2102
  signal,
1952
2103
  sessionId
1953
2104
  );
2105
+ throwIfGenerationErrorResponse(chartResponse);
1954
2106
  chart = {
1955
2107
  vegaLiteSpec: chartResponse.chart ? {
1956
2108
  ...chartResponse.chart,
@@ -1978,6 +2130,13 @@ async function ask(client, queryEngine, question, options, signal) {
1978
2130
  trace: queryResponse.data.trace
1979
2131
  };
1980
2132
  } catch (error) {
2133
+ if (isAiProviderPipelineError(error)) {
2134
+ if (error instanceof QueryPipelineError) {
2135
+ throw error;
2136
+ }
2137
+ const providerError = error;
2138
+ throw new QueryPipelineError(providerError.message, providerError.code);
2139
+ }
1981
2140
  attempt++;
1982
2141
  if (attempt > maxRetry) {
1983
2142
  throw error;
@@ -2274,6 +2433,7 @@ async function modifyChart(client, queryEngine, input, options, signal) {
2274
2433
  signal,
2275
2434
  sessionId
2276
2435
  );
2436
+ throwIfGenerationErrorResponse(vizspecResponse);
2277
2437
  chart = {
2278
2438
  vizSpec: vizspecResponse.spec,
2279
2439
  specType: "vizspec",
@@ -2299,6 +2459,7 @@ async function modifyChart(client, queryEngine, input, options, signal) {
2299
2459
  signal,
2300
2460
  sessionId
2301
2461
  );
2462
+ throwIfGenerationErrorResponse(chartResponse);
2302
2463
  chart = {
2303
2464
  vegaLiteSpec: chartResponse.chart ? {
2304
2465
  ...chartResponse.chart,
@@ -3468,6 +3629,8 @@ var QueryPanelSdkAPI = class _QueryPanelSdkAPI {
3468
3629
  QueryErrorCode,
3469
3630
  QueryPanelSdkAPI,
3470
3631
  QueryPipelineError,
3471
- anonymizeResults
3632
+ anonymizeResults,
3633
+ mapGeneratedParams,
3634
+ normalizeSqlParamsInput
3472
3635
  });
3473
3636
  //# sourceMappingURL=index.cjs.map