@querypanel/node-sdk 1.0.36 → 1.0.38

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
@@ -10,7 +10,7 @@ bun add @querypanel/sdk
10
10
  npm install @querypanel/sdk
11
11
  ```
12
12
 
13
- > **Runtime:** Node.js 18+ (or Bun). The SDK relies on the native `fetch` API.
13
+ > **Runtime:** Node.js 18+, Deno, or Bun. The SDK uses Web Crypto API for JWT signing and the native `fetch` API, making it compatible with modern JavaScript runtimes.
14
14
 
15
15
  ## Quickstart
16
16
 
@@ -133,6 +133,25 @@ dashboard.data.forEach(item => {
133
133
  });
134
134
  ```
135
135
 
136
+ ## Deno Support
137
+
138
+ The SDK is fully compatible with Deno (including Supabase Edge Functions) thanks to its use of Web Crypto API for JWT signing. No additional configuration needed:
139
+
140
+ ```ts
141
+ import { QueryPanelSdkAPI } from "https://esm.sh/@querypanel/sdk";
142
+
143
+ const qp = new QueryPanelSdkAPI(
144
+ Deno.env.get("QUERYPANEL_URL")!,
145
+ Deno.env.get("PRIVATE_KEY")!,
146
+ Deno.env.get("ORGANIZATION_ID")!,
147
+ );
148
+
149
+ // Use the SDK as normal - JWT signing works automatically
150
+ const response = await qp.ask("Show top products", {
151
+ tenantId: "tenant_123",
152
+ });
153
+ ```
154
+
136
155
  ## Building locally
137
156
 
138
157
  ```bash
package/dist/index.cjs CHANGED
@@ -554,7 +554,6 @@ function sanitize2(value) {
554
554
  }
555
555
 
556
556
  // src/core/client.ts
557
- var import_node_crypto = require("crypto");
558
557
  var ApiClient = class {
559
558
  baseUrl;
560
559
  privateKey;
@@ -562,6 +561,7 @@ var ApiClient = class {
562
561
  defaultTenantId;
563
562
  additionalHeaders;
564
563
  fetchImpl;
564
+ cryptoKey = null;
565
565
  constructor(baseUrl, privateKey, organizationId, options) {
566
566
  if (!baseUrl) {
567
567
  throw new Error("Base URL is required");
@@ -677,6 +677,66 @@ var ApiClient = class {
677
677
  }
678
678
  return headers;
679
679
  }
680
+ /**
681
+ * Base64URL encode a string (works in both Node.js 18+ and Deno)
682
+ */
683
+ base64UrlEncode(str) {
684
+ const bytes = new TextEncoder().encode(str);
685
+ let binary = "";
686
+ const chunkSize = 8192;
687
+ for (let i = 0; i < bytes.length; i += chunkSize) {
688
+ const chunk = bytes.slice(i, i + chunkSize);
689
+ binary += String.fromCharCode(...chunk);
690
+ }
691
+ const base64 = btoa(binary);
692
+ return base64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, "");
693
+ }
694
+ /**
695
+ * Base64URL encode from Uint8Array (for binary data like signatures)
696
+ */
697
+ base64UrlEncodeBytes(bytes) {
698
+ let binary = "";
699
+ const chunkSize = 8192;
700
+ for (let i = 0; i < bytes.length; i += chunkSize) {
701
+ const chunk = bytes.slice(i, i + chunkSize);
702
+ binary += String.fromCharCode(...chunk);
703
+ }
704
+ const base64 = btoa(binary);
705
+ return base64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, "");
706
+ }
707
+ /**
708
+ * Import the private key into Web Crypto API format (cached after first import)
709
+ */
710
+ async getCryptoKey() {
711
+ if (this.cryptoKey) {
712
+ return this.cryptoKey;
713
+ }
714
+ this.cryptoKey = await crypto.subtle.importKey(
715
+ "pkcs8",
716
+ this.privateKeyToArrayBuffer(this.privateKey),
717
+ {
718
+ name: "RSASSA-PKCS1-v1_5",
719
+ hash: "SHA-256"
720
+ },
721
+ false,
722
+ ["sign"]
723
+ );
724
+ return this.cryptoKey;
725
+ }
726
+ /**
727
+ * Convert PEM private key to ArrayBuffer for Web Crypto API
728
+ */
729
+ privateKeyToArrayBuffer(pem) {
730
+ const pemHeader = "-----BEGIN PRIVATE KEY-----";
731
+ const pemFooter = "-----END PRIVATE KEY-----";
732
+ const pemContents = pem.replace(pemHeader, "").replace(pemFooter, "").replace(/\s/g, "");
733
+ const binaryString = atob(pemContents);
734
+ const bytes = new Uint8Array(binaryString.length);
735
+ for (let i = 0; i < binaryString.length; i++) {
736
+ bytes[i] = binaryString.charCodeAt(i);
737
+ }
738
+ return bytes.buffer;
739
+ }
680
740
  async generateJWT(tenantId, userId, scopes) {
681
741
  const header = {
682
742
  alg: "RS256",
@@ -688,19 +748,20 @@ var ApiClient = class {
688
748
  };
689
749
  if (userId) payload.userId = userId;
690
750
  if (scopes?.length) payload.scopes = scopes;
691
- const encodeJson = (obj) => {
692
- const json = JSON.stringify(obj);
693
- const base64 = Buffer.from(json).toString("base64");
694
- return base64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, "");
695
- };
696
- const encodedHeader = encodeJson(header);
697
- const encodedPayload = encodeJson(payload);
751
+ const encodedHeader = this.base64UrlEncode(JSON.stringify(header));
752
+ const encodedPayload = this.base64UrlEncode(JSON.stringify(payload));
698
753
  const data = `${encodedHeader}.${encodedPayload}`;
699
- const signer = (0, import_node_crypto.createSign)("RSA-SHA256");
700
- signer.update(data);
701
- signer.end();
702
- const signature = signer.sign(this.privateKey);
703
- const encodedSignature = signature.toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, "");
754
+ const key = await this.getCryptoKey();
755
+ const dataBytes = new TextEncoder().encode(data);
756
+ const signature = await crypto.subtle.sign(
757
+ {
758
+ name: "RSASSA-PKCS1-v1_5"
759
+ },
760
+ key,
761
+ dataBytes
762
+ );
763
+ const signatureBytes = new Uint8Array(signature);
764
+ const encodedSignature = this.base64UrlEncodeBytes(signatureBytes);
704
765
  return `${data}.${encodedSignature}`;
705
766
  }
706
767
  };
@@ -785,13 +846,19 @@ var QueryEngine = class {
785
846
  return sql;
786
847
  }
787
848
  const tenantField = metadata.tenantFieldName;
788
- const paramKey = tenantField;
789
- params[paramKey] = tenantId;
790
849
  const normalizedSql = sql.toLowerCase();
791
850
  if (normalizedSql.includes(tenantField.toLowerCase())) {
792
851
  return sql;
793
852
  }
794
- const tenantPredicate = metadata.dialect === "clickhouse" ? `${tenantField} = {${tenantField}:${metadata.tenantFieldType ?? "String"}}` : `${tenantField} = '${tenantId}'`;
853
+ let tenantPredicate;
854
+ if (metadata.dialect === "clickhouse") {
855
+ const paramKey = tenantField;
856
+ params[paramKey] = tenantId;
857
+ tenantPredicate = `${tenantField} = {${tenantField}:${metadata.tenantFieldType ?? "String"}}`;
858
+ } else {
859
+ const escapedId = tenantId.replace(/'/g, "''");
860
+ tenantPredicate = `${tenantField} = '${escapedId}'`;
861
+ }
795
862
  if (/\bwhere\b/i.test(sql)) {
796
863
  return sql.replace(
797
864
  /\bwhere\b/i,
@@ -1025,7 +1092,6 @@ function resolveTenantId2(client, tenantId) {
1025
1092
  }
1026
1093
 
1027
1094
  // src/routes/ingest.ts
1028
- var import_node_crypto2 = require("crypto");
1029
1095
  async function syncSchema(client, queryEngine, databaseName, options, signal) {
1030
1096
  const tenantId = resolveTenantId3(client, options.tenantId);
1031
1097
  const adapter = queryEngine.getDatabase(databaseName);
@@ -1037,7 +1103,7 @@ async function syncSchema(client, queryEngine, databaseName, options, signal) {
1037
1103
  if (options.forceReindex) {
1038
1104
  payload.force_reindex = true;
1039
1105
  }
1040
- const sessionId = (0, import_node_crypto2.randomUUID)();
1106
+ const sessionId = crypto.randomUUID();
1041
1107
  const response = await client.post(
1042
1108
  "/ingest",
1043
1109
  payload,
@@ -1086,10 +1152,9 @@ function buildSchemaRequest(databaseName, adapter, introspection, metadata) {
1086
1152
  }
1087
1153
 
1088
1154
  // src/routes/query.ts
1089
- var import_node_crypto3 = require("crypto");
1090
1155
  async function ask(client, queryEngine, question, options, signal) {
1091
1156
  const tenantId = resolveTenantId4(client, options.tenantId);
1092
- const sessionId = (0, import_node_crypto3.randomUUID)();
1157
+ const sessionId = crypto.randomUUID();
1093
1158
  const maxRetry = options.maxRetry ?? 0;
1094
1159
  let attempt = 0;
1095
1160
  let lastError = options.lastError;