@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 +20 -1
- package/dist/index.cjs +85 -20
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +85 -20
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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
|
|
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
|
|
692
|
-
|
|
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
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
1157
|
+
const sessionId = crypto.randomUUID();
|
|
1093
1158
|
const maxRetry = options.maxRetry ?? 0;
|
|
1094
1159
|
let attempt = 0;
|
|
1095
1160
|
let lastError = options.lastError;
|