@querypanel/node-sdk 1.0.56 → 1.0.58
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 +15 -2
- package/dist/index.cjs +917 -64
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +534 -9
- package/dist/index.d.ts +534 -9
- package/dist/index.js +915 -64
- package/dist/index.js.map +1 -1
- package/package.json +3 -2
package/dist/index.js
CHANGED
|
@@ -1,3 +1,183 @@
|
|
|
1
|
+
// src/adapters/bigquery.ts
|
|
2
|
+
var BigQueryAdapter = class {
|
|
3
|
+
constructor(clientFn, options) {
|
|
4
|
+
this.clientFn = clientFn;
|
|
5
|
+
this.projectId = options.projectId;
|
|
6
|
+
this.datasetProjectId = options.datasetProjectId ?? options.projectId;
|
|
7
|
+
this.defaultDataset = options.dataset;
|
|
8
|
+
this.databaseName = options.database ?? options.dataset;
|
|
9
|
+
this.kind = options.kind ?? "bigquery";
|
|
10
|
+
if (options.allowedTables) {
|
|
11
|
+
this.allowedTables = normalizeTableFilter(
|
|
12
|
+
options.allowedTables,
|
|
13
|
+
this.defaultDataset
|
|
14
|
+
);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
projectId;
|
|
18
|
+
datasetProjectId;
|
|
19
|
+
defaultDataset;
|
|
20
|
+
databaseName;
|
|
21
|
+
kind;
|
|
22
|
+
allowedTables;
|
|
23
|
+
async execute(sql, params) {
|
|
24
|
+
if (this.allowedTables) {
|
|
25
|
+
this.validateQueryTables(sql);
|
|
26
|
+
}
|
|
27
|
+
const result = await this.clientFn({
|
|
28
|
+
query: sql,
|
|
29
|
+
params,
|
|
30
|
+
dryRun: false
|
|
31
|
+
});
|
|
32
|
+
return { fields: result.fields, rows: result.rows };
|
|
33
|
+
}
|
|
34
|
+
async validate(sql, params) {
|
|
35
|
+
await this.clientFn({
|
|
36
|
+
query: sql,
|
|
37
|
+
params,
|
|
38
|
+
dryRun: true
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
getDialect() {
|
|
42
|
+
return "bigquery";
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Introspect using BigQuery INFORMATION_SCHEMA (TABLES and COLUMNS).
|
|
46
|
+
* Restricts to the default dataset (or allowedTables) and returns tables/columns only.
|
|
47
|
+
*/
|
|
48
|
+
async introspect(options) {
|
|
49
|
+
const tablesToIntrospect = options?.tables ? normalizeTableFilter(options.tables, this.defaultDataset) : this.allowedTables;
|
|
50
|
+
const normalizedTables = tablesToIntrospect ?? [];
|
|
51
|
+
const tablesSql = buildTablesQuery(
|
|
52
|
+
this.datasetProjectId,
|
|
53
|
+
this.defaultDataset,
|
|
54
|
+
normalizedTables
|
|
55
|
+
);
|
|
56
|
+
const tablesResult = await this.clientFn({
|
|
57
|
+
query: tablesSql,
|
|
58
|
+
dryRun: false
|
|
59
|
+
});
|
|
60
|
+
const tableRows = tablesResult.rows;
|
|
61
|
+
const columnsSql = buildColumnsQuery(
|
|
62
|
+
this.datasetProjectId,
|
|
63
|
+
this.defaultDataset,
|
|
64
|
+
normalizedTables
|
|
65
|
+
);
|
|
66
|
+
const columnsResult = await this.clientFn({
|
|
67
|
+
query: columnsSql,
|
|
68
|
+
dryRun: false
|
|
69
|
+
});
|
|
70
|
+
const columnRows = columnsResult.rows;
|
|
71
|
+
const tablesByKey = /* @__PURE__ */ new Map();
|
|
72
|
+
for (const row of tableRows) {
|
|
73
|
+
const key = tableKey(row.table_schema, row.table_name);
|
|
74
|
+
const table = {
|
|
75
|
+
name: row.table_name,
|
|
76
|
+
schema: row.table_schema,
|
|
77
|
+
type: asTableType(row.table_type),
|
|
78
|
+
columns: []
|
|
79
|
+
};
|
|
80
|
+
tablesByKey.set(key, table);
|
|
81
|
+
}
|
|
82
|
+
for (const row of columnRows) {
|
|
83
|
+
const key = tableKey(row.table_schema, row.table_name);
|
|
84
|
+
const table = tablesByKey.get(key);
|
|
85
|
+
if (!table) continue;
|
|
86
|
+
const column = {
|
|
87
|
+
name: row.column_name,
|
|
88
|
+
type: row.data_type,
|
|
89
|
+
isPrimaryKey: false
|
|
90
|
+
};
|
|
91
|
+
table.columns.push(column);
|
|
92
|
+
}
|
|
93
|
+
const tables = Array.from(tablesByKey.values()).sort((a, b) => {
|
|
94
|
+
if (a.schema === b.schema) {
|
|
95
|
+
return a.name.localeCompare(b.name);
|
|
96
|
+
}
|
|
97
|
+
return a.schema.localeCompare(b.schema);
|
|
98
|
+
});
|
|
99
|
+
return {
|
|
100
|
+
db: {
|
|
101
|
+
kind: this.kind,
|
|
102
|
+
name: this.databaseName
|
|
103
|
+
},
|
|
104
|
+
tables,
|
|
105
|
+
introspectedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
validateQueryTables(sql) {
|
|
109
|
+
if (!this.allowedTables || this.allowedTables.length === 0) {
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
const allowedSet = new Set(
|
|
113
|
+
this.allowedTables.map((t) => tableKey(t.dataset, t.table))
|
|
114
|
+
);
|
|
115
|
+
const tablePattern = /(?:FROM|JOIN)\s+`?(?:[\w-]+\.)?([\w-]+)\.([\w-]+)`?|(?:FROM|JOIN)\s+`?([\w-]+)`?/gi;
|
|
116
|
+
const matches = sql.matchAll(tablePattern);
|
|
117
|
+
for (const match of matches) {
|
|
118
|
+
const dataset = match[1] ?? match[3] ?? this.defaultDataset;
|
|
119
|
+
const table = match[2] ?? match[3];
|
|
120
|
+
if (table) {
|
|
121
|
+
const key = tableKey(dataset, table);
|
|
122
|
+
if (!allowedSet.has(key)) {
|
|
123
|
+
throw new Error(
|
|
124
|
+
`Query references table "${dataset}.${table}" which is not in the allowed tables list`
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
function normalizeTableFilter(tables, defaultDataset) {
|
|
132
|
+
if (!tables?.length) return [];
|
|
133
|
+
const normalized = [];
|
|
134
|
+
const seen = /* @__PURE__ */ new Set();
|
|
135
|
+
for (const raw of tables) {
|
|
136
|
+
if (!raw?.trim()) continue;
|
|
137
|
+
const trimmed = raw.trim();
|
|
138
|
+
const parts = trimmed.split(".");
|
|
139
|
+
const table = parts.pop() ?? "";
|
|
140
|
+
const dataset = parts.pop() ?? defaultDataset;
|
|
141
|
+
const key = tableKey(dataset, table);
|
|
142
|
+
if (seen.has(key)) continue;
|
|
143
|
+
seen.add(key);
|
|
144
|
+
normalized.push({ dataset, table });
|
|
145
|
+
}
|
|
146
|
+
return normalized;
|
|
147
|
+
}
|
|
148
|
+
function tableKey(dataset, table) {
|
|
149
|
+
return `${dataset}.${table}`;
|
|
150
|
+
}
|
|
151
|
+
function buildTablesQuery(datasetProjectId, dataset, tables) {
|
|
152
|
+
const qualifier = `\`${datasetProjectId}.${dataset}.INFORMATION_SCHEMA.TABLES\``;
|
|
153
|
+
const filter = tables.length > 0 ? ` AND table_name IN (${tables.map((t) => `'${escapeSql(t.table)}'`).join(", ")})` : "";
|
|
154
|
+
return `SELECT table_name, table_schema, table_type
|
|
155
|
+
FROM ${qualifier}
|
|
156
|
+
WHERE table_schema = '${escapeSql(dataset)}'${filter}
|
|
157
|
+
ORDER BY table_schema, table_name`;
|
|
158
|
+
}
|
|
159
|
+
function buildColumnsQuery(datasetProjectId, dataset, tables) {
|
|
160
|
+
const qualifier = `\`${datasetProjectId}.${dataset}.INFORMATION_SCHEMA.COLUMNS\``;
|
|
161
|
+
const filter = tables.length > 0 ? ` AND table_name IN (${tables.map((t) => `'${escapeSql(t.table)}'`).join(", ")})` : "";
|
|
162
|
+
return `SELECT table_name, table_schema, column_name, data_type, ordinal_position, is_nullable
|
|
163
|
+
FROM ${qualifier}
|
|
164
|
+
WHERE table_schema = '${escapeSql(dataset)}'${filter}
|
|
165
|
+
ORDER BY table_schema, table_name, ordinal_position`;
|
|
166
|
+
}
|
|
167
|
+
function escapeSql(value) {
|
|
168
|
+
return value.replace(/\\/g, "\\\\").replace(/'/g, "''");
|
|
169
|
+
}
|
|
170
|
+
function asTableType(tableType) {
|
|
171
|
+
const normalized = tableType?.toLowerCase() ?? "";
|
|
172
|
+
if (normalized.includes("view")) {
|
|
173
|
+
return normalized.includes("materialized") ? "materialized_view" : "view";
|
|
174
|
+
}
|
|
175
|
+
if (normalized.includes("external") || normalized.includes("snapshot") || normalized.includes("clone")) {
|
|
176
|
+
return "table";
|
|
177
|
+
}
|
|
178
|
+
return "table";
|
|
179
|
+
}
|
|
180
|
+
|
|
1
181
|
// src/utils/clickhouse.ts
|
|
2
182
|
var WRAPPER_REGEX = /^(Nullable|LowCardinality|SimpleAggregateFunction)\((.+)\)$/i;
|
|
3
183
|
function unwrapTypeModifiers(type) {
|
|
@@ -67,7 +247,7 @@ var ClickHouseAdapter = class {
|
|
|
67
247
|
this.defaultFormat = options.defaultFormat ?? "JSONEachRow";
|
|
68
248
|
this.kind = options.kind ?? "clickhouse";
|
|
69
249
|
if (options.allowedTables) {
|
|
70
|
-
this.allowedTables =
|
|
250
|
+
this.allowedTables = normalizeTableFilter2(options.allowedTables);
|
|
71
251
|
}
|
|
72
252
|
}
|
|
73
253
|
databaseName;
|
|
@@ -105,7 +285,7 @@ var ClickHouseAdapter = class {
|
|
|
105
285
|
* No indexes, constraints, or statistics
|
|
106
286
|
*/
|
|
107
287
|
async introspect(options) {
|
|
108
|
-
const tablesToIntrospect = options?.tables ?
|
|
288
|
+
const tablesToIntrospect = options?.tables ? normalizeTableFilter2(options.tables) : this.allowedTables;
|
|
109
289
|
const allowTables = tablesToIntrospect ?? [];
|
|
110
290
|
const hasFilter = allowTables.length > 0;
|
|
111
291
|
const queryParams = {
|
|
@@ -145,7 +325,7 @@ var ClickHouseAdapter = class {
|
|
|
145
325
|
const base = {
|
|
146
326
|
name: table.name,
|
|
147
327
|
schema: this.databaseName,
|
|
148
|
-
type:
|
|
328
|
+
type: asTableType2(table.engine),
|
|
149
329
|
columns: tableColumns
|
|
150
330
|
};
|
|
151
331
|
const comment = sanitize(table.comment);
|
|
@@ -223,7 +403,7 @@ function normalizePayload(payload) {
|
|
|
223
403
|
}
|
|
224
404
|
return [];
|
|
225
405
|
}
|
|
226
|
-
function
|
|
406
|
+
function normalizeTableFilter2(tables) {
|
|
227
407
|
if (!tables?.length) return [];
|
|
228
408
|
const seen = /* @__PURE__ */ new Set();
|
|
229
409
|
const normalized = [];
|
|
@@ -251,7 +431,7 @@ function transformColumnRow(row) {
|
|
|
251
431
|
if (comment !== void 0) column.comment = comment;
|
|
252
432
|
return column;
|
|
253
433
|
}
|
|
254
|
-
function
|
|
434
|
+
function asTableType2(engine) {
|
|
255
435
|
if (typeof engine === "string") {
|
|
256
436
|
const normalized = engine.toLowerCase();
|
|
257
437
|
if (normalized.includes("view")) {
|
|
@@ -280,7 +460,7 @@ var PostgresAdapter = class {
|
|
|
280
460
|
this.defaultSchema = options.defaultSchema ?? "public";
|
|
281
461
|
this.kind = options.kind ?? "postgres";
|
|
282
462
|
if (options.allowedTables) {
|
|
283
|
-
this.allowedTables =
|
|
463
|
+
this.allowedTables = normalizeTableFilter3(
|
|
284
464
|
options.allowedTables,
|
|
285
465
|
this.defaultSchema
|
|
286
466
|
);
|
|
@@ -307,7 +487,7 @@ var PostgresAdapter = class {
|
|
|
307
487
|
return;
|
|
308
488
|
}
|
|
309
489
|
const allowedSet = new Set(
|
|
310
|
-
this.allowedTables.map((t) =>
|
|
490
|
+
this.allowedTables.map((t) => tableKey2(t.schema, t.table))
|
|
311
491
|
);
|
|
312
492
|
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*/)");
|
|
313
493
|
const tablePattern = /(?:FROM|JOIN)\s+(?:ONLY\s+)?(?:([a-zA-Z_][a-zA-Z0-9_]*)\.)?(["']?[a-zA-Z_][a-zA-Z0-9_]*["']?)/gi;
|
|
@@ -316,7 +496,7 @@ var PostgresAdapter = class {
|
|
|
316
496
|
const schema = match[1] ?? this.defaultSchema;
|
|
317
497
|
const table = match[2]?.replace(/['"]/g, "");
|
|
318
498
|
if (table) {
|
|
319
|
-
const key =
|
|
499
|
+
const key = tableKey2(schema, table);
|
|
320
500
|
if (!allowedSet.has(key)) {
|
|
321
501
|
throw new Error(
|
|
322
502
|
`Query references table "${schema}.${table}" which is not in the allowed tables list`
|
|
@@ -326,29 +506,31 @@ var PostgresAdapter = class {
|
|
|
326
506
|
}
|
|
327
507
|
}
|
|
328
508
|
/**
|
|
329
|
-
* Convert
|
|
330
|
-
* PostgreSQL expects $1, $2, $3 in SQL and an array of values [val1, val2, val3]
|
|
509
|
+
* Convert params to positional array for PostgreSQL.
|
|
510
|
+
* PostgreSQL expects $1, $2, $3 in SQL and an array of values [val1, val2, val3].
|
|
511
|
+
* When the record has numeric keys ("1", "2", "3") — e.g. from mapGeneratedParams —
|
|
512
|
+
* use only those in order so placeholder order matches SQL. Otherwise fall back to
|
|
513
|
+
* named keys in alphabetical order for backward compatibility.
|
|
331
514
|
*/
|
|
332
515
|
convertNamedToPositionalParams(params) {
|
|
333
516
|
const numericKeys = Object.keys(params).filter((k) => /^\d+$/.test(k)).map((k) => Number.parseInt(k, 10)).sort((a, b) => a - b);
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
517
|
+
if (numericKeys.length > 0) {
|
|
518
|
+
const positionalParams = [];
|
|
519
|
+
for (const key of numericKeys) {
|
|
520
|
+
let val = params[String(key)];
|
|
521
|
+
if (typeof val === "string") {
|
|
522
|
+
const match = val.match(/^<([a-zA-Z0-9_]+)>$/);
|
|
523
|
+
const namedKey = match?.[1];
|
|
524
|
+
if (namedKey && namedKey in params) {
|
|
525
|
+
val = params[namedKey];
|
|
526
|
+
}
|
|
343
527
|
}
|
|
528
|
+
positionalParams.push(val);
|
|
344
529
|
}
|
|
345
|
-
positionalParams
|
|
346
|
-
}
|
|
347
|
-
for (const key of namedKeys) {
|
|
348
|
-
const val = params[key];
|
|
349
|
-
positionalParams.push(val);
|
|
530
|
+
return positionalParams;
|
|
350
531
|
}
|
|
351
|
-
|
|
532
|
+
const namedKeys = Object.keys(params).filter((k) => !/^\d+$/.test(k)).sort();
|
|
533
|
+
return namedKeys.map((key) => params[key]);
|
|
352
534
|
}
|
|
353
535
|
async validate(sql, params) {
|
|
354
536
|
let paramArray;
|
|
@@ -365,23 +547,23 @@ var PostgresAdapter = class {
|
|
|
365
547
|
* No indexes, constraints, or statistics
|
|
366
548
|
*/
|
|
367
549
|
async introspect(options) {
|
|
368
|
-
const tablesToIntrospect = options?.tables ?
|
|
550
|
+
const tablesToIntrospect = options?.tables ? normalizeTableFilter3(options.tables, this.defaultSchema) : this.allowedTables;
|
|
369
551
|
const normalizedTables = tablesToIntrospect ?? [];
|
|
370
552
|
const tablesResult = await this.clientFn(
|
|
371
|
-
|
|
553
|
+
buildTablesQuery2(normalizedTables)
|
|
372
554
|
);
|
|
373
555
|
const tableRows = tablesResult.rows;
|
|
374
556
|
const columnsResult = await this.clientFn(
|
|
375
|
-
|
|
557
|
+
buildColumnsQuery2(normalizedTables)
|
|
376
558
|
);
|
|
377
559
|
const columnRows = columnsResult.rows;
|
|
378
560
|
const tablesByKey = /* @__PURE__ */ new Map();
|
|
379
561
|
for (const row of tableRows) {
|
|
380
|
-
const key =
|
|
562
|
+
const key = tableKey2(row.schema_name, row.table_name);
|
|
381
563
|
const table = {
|
|
382
564
|
name: row.table_name,
|
|
383
565
|
schema: row.schema_name,
|
|
384
|
-
type:
|
|
566
|
+
type: asTableType3(row.table_type),
|
|
385
567
|
columns: []
|
|
386
568
|
};
|
|
387
569
|
const comment = sanitize2(row.comment);
|
|
@@ -391,7 +573,7 @@ var PostgresAdapter = class {
|
|
|
391
573
|
tablesByKey.set(key, table);
|
|
392
574
|
}
|
|
393
575
|
for (const row of columnRows) {
|
|
394
|
-
const key =
|
|
576
|
+
const key = tableKey2(row.table_schema, row.table_name);
|
|
395
577
|
const table = tablesByKey.get(key);
|
|
396
578
|
if (!table) continue;
|
|
397
579
|
const column = {
|
|
@@ -421,7 +603,7 @@ var PostgresAdapter = class {
|
|
|
421
603
|
};
|
|
422
604
|
}
|
|
423
605
|
};
|
|
424
|
-
function
|
|
606
|
+
function normalizeTableFilter3(tables, defaultSchema) {
|
|
425
607
|
if (!tables?.length) return [];
|
|
426
608
|
const normalized = [];
|
|
427
609
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -435,14 +617,14 @@ function normalizeTableFilter2(tables, defaultSchema) {
|
|
|
435
617
|
if (!isSafeIdentifier(schema) || !isSafeIdentifier(table)) {
|
|
436
618
|
continue;
|
|
437
619
|
}
|
|
438
|
-
const key =
|
|
620
|
+
const key = tableKey2(schema, table);
|
|
439
621
|
if (seen.has(key)) continue;
|
|
440
622
|
seen.add(key);
|
|
441
623
|
normalized.push({ schema, table });
|
|
442
624
|
}
|
|
443
625
|
return normalized;
|
|
444
626
|
}
|
|
445
|
-
function
|
|
627
|
+
function buildTablesQuery2(tables) {
|
|
446
628
|
const filter = buildFilterClause(tables, "n.nspname", "c.relname");
|
|
447
629
|
return `SELECT
|
|
448
630
|
c.relname AS table_name,
|
|
@@ -461,7 +643,7 @@ function buildTablesQuery(tables) {
|
|
|
461
643
|
${filter}
|
|
462
644
|
ORDER BY n.nspname, c.relname;`;
|
|
463
645
|
}
|
|
464
|
-
function
|
|
646
|
+
function buildColumnsQuery2(tables) {
|
|
465
647
|
const filter = buildFilterClause(
|
|
466
648
|
tables,
|
|
467
649
|
"cols.table_schema",
|
|
@@ -506,13 +688,13 @@ function buildFilterClause(tables, schemaExpr, tableExpr) {
|
|
|
506
688
|
});
|
|
507
689
|
return `AND (${clauses.join(" OR ")})`;
|
|
508
690
|
}
|
|
509
|
-
function
|
|
691
|
+
function tableKey2(schema, table) {
|
|
510
692
|
return `${schema}.${table}`;
|
|
511
693
|
}
|
|
512
694
|
function isSafeIdentifier(value) {
|
|
513
695
|
return /^[A-Za-z_][A-Za-z0-9_]*$/.test(value);
|
|
514
696
|
}
|
|
515
|
-
function
|
|
697
|
+
function asTableType3(value) {
|
|
516
698
|
const normalized = value.toLowerCase();
|
|
517
699
|
if (normalized.includes("view")) {
|
|
518
700
|
return normalized.includes("materialized") ? "materialized_view" : "view";
|
|
@@ -560,6 +742,13 @@ var ApiClient = class {
|
|
|
560
742
|
getDefaultTenantId() {
|
|
561
743
|
return this.defaultTenantId;
|
|
562
744
|
}
|
|
745
|
+
/**
|
|
746
|
+
* Create a JWT for the given tenant (and optional userId, scopes).
|
|
747
|
+
* Use this when you need to pass a token to the embed (e.g. frontend or demo).
|
|
748
|
+
*/
|
|
749
|
+
async createJwt(tenantId, userId, scopes) {
|
|
750
|
+
return await this.generateJWT(tenantId, userId, scopes);
|
|
751
|
+
}
|
|
563
752
|
async get(path, tenantId, userId, scopes, signal, sessionId) {
|
|
564
753
|
return await this.request(path, {
|
|
565
754
|
method: "GET",
|
|
@@ -772,6 +961,111 @@ var ApiClient = class {
|
|
|
772
961
|
}
|
|
773
962
|
};
|
|
774
963
|
|
|
964
|
+
// src/core/callback-client.ts
|
|
965
|
+
var CallbackApiClient = class {
|
|
966
|
+
requestHandler;
|
|
967
|
+
defaultTenantId;
|
|
968
|
+
constructor(requestHandler, options) {
|
|
969
|
+
this.requestHandler = requestHandler;
|
|
970
|
+
this.defaultTenantId = options?.defaultTenantId;
|
|
971
|
+
}
|
|
972
|
+
getDefaultTenantId() {
|
|
973
|
+
return this.defaultTenantId;
|
|
974
|
+
}
|
|
975
|
+
async request(opts) {
|
|
976
|
+
const { data, headers } = await this.requestHandler({
|
|
977
|
+
method: opts.method,
|
|
978
|
+
path: opts.path,
|
|
979
|
+
body: opts.body,
|
|
980
|
+
tenantId: opts.tenantId,
|
|
981
|
+
userId: opts.userId,
|
|
982
|
+
scopes: opts.scopes,
|
|
983
|
+
signal: opts.signal,
|
|
984
|
+
sessionId: opts.sessionId
|
|
985
|
+
});
|
|
986
|
+
return { data, headers };
|
|
987
|
+
}
|
|
988
|
+
async get(path, tenantId, userId, scopes, signal, sessionId) {
|
|
989
|
+
const { data } = await this.request({
|
|
990
|
+
method: "GET",
|
|
991
|
+
path,
|
|
992
|
+
tenantId,
|
|
993
|
+
userId,
|
|
994
|
+
scopes,
|
|
995
|
+
signal,
|
|
996
|
+
sessionId
|
|
997
|
+
});
|
|
998
|
+
return data;
|
|
999
|
+
}
|
|
1000
|
+
async post(path, body, tenantId, userId, scopes, signal, sessionId) {
|
|
1001
|
+
const { data } = await this.request({
|
|
1002
|
+
method: "POST",
|
|
1003
|
+
path,
|
|
1004
|
+
body,
|
|
1005
|
+
tenantId,
|
|
1006
|
+
userId,
|
|
1007
|
+
scopes,
|
|
1008
|
+
signal,
|
|
1009
|
+
sessionId
|
|
1010
|
+
});
|
|
1011
|
+
return data;
|
|
1012
|
+
}
|
|
1013
|
+
async postWithHeaders(path, body, tenantId, userId, scopes, signal, sessionId) {
|
|
1014
|
+
const { data, headers } = await this.request({
|
|
1015
|
+
method: "POST",
|
|
1016
|
+
path,
|
|
1017
|
+
body,
|
|
1018
|
+
tenantId,
|
|
1019
|
+
userId,
|
|
1020
|
+
scopes,
|
|
1021
|
+
signal,
|
|
1022
|
+
sessionId
|
|
1023
|
+
});
|
|
1024
|
+
return {
|
|
1025
|
+
data,
|
|
1026
|
+
headers: headers ?? new Headers()
|
|
1027
|
+
};
|
|
1028
|
+
}
|
|
1029
|
+
async put(path, body, tenantId, userId, scopes, signal, sessionId) {
|
|
1030
|
+
const { data } = await this.request({
|
|
1031
|
+
method: "PUT",
|
|
1032
|
+
path,
|
|
1033
|
+
body,
|
|
1034
|
+
tenantId,
|
|
1035
|
+
userId,
|
|
1036
|
+
scopes,
|
|
1037
|
+
signal,
|
|
1038
|
+
sessionId
|
|
1039
|
+
});
|
|
1040
|
+
return data;
|
|
1041
|
+
}
|
|
1042
|
+
async patch(path, body, tenantId, userId, scopes, signal, sessionId) {
|
|
1043
|
+
const { data } = await this.request({
|
|
1044
|
+
method: "PATCH",
|
|
1045
|
+
path,
|
|
1046
|
+
body,
|
|
1047
|
+
tenantId,
|
|
1048
|
+
userId,
|
|
1049
|
+
scopes,
|
|
1050
|
+
signal,
|
|
1051
|
+
sessionId
|
|
1052
|
+
});
|
|
1053
|
+
return data;
|
|
1054
|
+
}
|
|
1055
|
+
async delete(path, tenantId, userId, scopes, signal, sessionId) {
|
|
1056
|
+
const { data } = await this.request({
|
|
1057
|
+
method: "DELETE",
|
|
1058
|
+
path,
|
|
1059
|
+
tenantId,
|
|
1060
|
+
userId,
|
|
1061
|
+
scopes,
|
|
1062
|
+
signal,
|
|
1063
|
+
sessionId
|
|
1064
|
+
});
|
|
1065
|
+
return data;
|
|
1066
|
+
}
|
|
1067
|
+
};
|
|
1068
|
+
|
|
775
1069
|
// src/core/query-engine.ts
|
|
776
1070
|
var QueryEngine = class {
|
|
777
1071
|
databases = /* @__PURE__ */ new Map();
|
|
@@ -834,6 +1128,12 @@ var QueryEngine = class {
|
|
|
834
1128
|
return [];
|
|
835
1129
|
}
|
|
836
1130
|
}
|
|
1131
|
+
/**
|
|
1132
|
+
* Maps API params (array in SQL placeholder order $1, $2, $3) to ParamRecord.
|
|
1133
|
+
* Sets both numeric keys ("1", "2", "3") and named keys so that:
|
|
1134
|
+
* - Postgres adapter uses numeric order for positional params (matches SQL).
|
|
1135
|
+
* - Named keys remain for tenant isolation / other lookups.
|
|
1136
|
+
*/
|
|
837
1137
|
mapGeneratedParams(params) {
|
|
838
1138
|
const record = {};
|
|
839
1139
|
params.forEach((param, index) => {
|
|
@@ -841,6 +1141,7 @@ var QueryEngine = class {
|
|
|
841
1141
|
if (value === void 0) {
|
|
842
1142
|
return;
|
|
843
1143
|
}
|
|
1144
|
+
record[String(index + 1)] = value;
|
|
844
1145
|
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);
|
|
845
1146
|
const key = nameCandidate.replace(/[{}:$]/g, "").trim();
|
|
846
1147
|
record[key] = value;
|
|
@@ -861,6 +1162,9 @@ var QueryEngine = class {
|
|
|
861
1162
|
const paramKey = tenantField;
|
|
862
1163
|
params[paramKey] = tenantId;
|
|
863
1164
|
tenantPredicate = `${tenantField} = {${tenantField}:${metadata.tenantFieldType ?? "String"}}`;
|
|
1165
|
+
} else if (metadata.dialect === "bigquery") {
|
|
1166
|
+
params[tenantField] = tenantId;
|
|
1167
|
+
tenantPredicate = `${tenantField} = @${tenantField}`;
|
|
864
1168
|
} else {
|
|
865
1169
|
const escapedId = tenantId.replace(/'/g, "''");
|
|
866
1170
|
tenantPredicate = `${tenantField} = '${escapedId}'`;
|
|
@@ -1159,20 +1463,171 @@ async function deleteActiveChart(client, id, options, signal) {
|
|
|
1159
1463
|
signal
|
|
1160
1464
|
);
|
|
1161
1465
|
}
|
|
1162
|
-
function resolveTenantId2(client, tenantId) {
|
|
1163
|
-
const resolved = tenantId ?? client.getDefaultTenantId();
|
|
1164
|
-
if (!resolved) {
|
|
1165
|
-
throw new Error(
|
|
1166
|
-
"tenantId is required. Provide it per request or via defaultTenantId option."
|
|
1167
|
-
);
|
|
1168
|
-
}
|
|
1169
|
-
return resolved;
|
|
1170
|
-
}
|
|
1466
|
+
function resolveTenantId2(client, tenantId) {
|
|
1467
|
+
const resolved = tenantId ?? client.getDefaultTenantId();
|
|
1468
|
+
if (!resolved) {
|
|
1469
|
+
throw new Error(
|
|
1470
|
+
"tenantId is required. Provide it per request or via defaultTenantId option."
|
|
1471
|
+
);
|
|
1472
|
+
}
|
|
1473
|
+
return resolved;
|
|
1474
|
+
}
|
|
1475
|
+
|
|
1476
|
+
// src/routes/dashboards.ts
|
|
1477
|
+
function resolveTenantId3(client, tenantId) {
|
|
1478
|
+
const resolved = tenantId ?? client.getDefaultTenantId();
|
|
1479
|
+
if (resolved === void 0) {
|
|
1480
|
+
throw new Error("tenantId is required (option or client default)");
|
|
1481
|
+
}
|
|
1482
|
+
return resolved;
|
|
1483
|
+
}
|
|
1484
|
+
async function createDashboard(client, body, options, signal) {
|
|
1485
|
+
const tenantId = resolveTenantId3(client, options?.tenantId);
|
|
1486
|
+
return await client.post(
|
|
1487
|
+
"/dashboards",
|
|
1488
|
+
body,
|
|
1489
|
+
tenantId,
|
|
1490
|
+
options?.userId,
|
|
1491
|
+
options?.scopes,
|
|
1492
|
+
signal
|
|
1493
|
+
);
|
|
1494
|
+
}
|
|
1495
|
+
async function listDashboards(client, options, signal) {
|
|
1496
|
+
const tenantId = resolveTenantId3(client, options?.tenantId);
|
|
1497
|
+
const params = new URLSearchParams();
|
|
1498
|
+
if (options?.pagination?.page) {
|
|
1499
|
+
params.set("page", options.pagination.page.toString());
|
|
1500
|
+
}
|
|
1501
|
+
if (options?.pagination?.limit) {
|
|
1502
|
+
params.set("limit", options.pagination.limit.toString());
|
|
1503
|
+
}
|
|
1504
|
+
if (options?.status) {
|
|
1505
|
+
params.set("status", options.status);
|
|
1506
|
+
}
|
|
1507
|
+
if (options?.sortBy) {
|
|
1508
|
+
params.set("sort_by", options.sortBy);
|
|
1509
|
+
}
|
|
1510
|
+
if (options?.sortDir) {
|
|
1511
|
+
params.set("sort_dir", options.sortDir);
|
|
1512
|
+
}
|
|
1513
|
+
return await client.get(
|
|
1514
|
+
`/dashboards?${params.toString()}`,
|
|
1515
|
+
tenantId,
|
|
1516
|
+
options?.userId,
|
|
1517
|
+
options?.scopes,
|
|
1518
|
+
signal
|
|
1519
|
+
);
|
|
1520
|
+
}
|
|
1521
|
+
async function getDashboard(client, id, options, signal) {
|
|
1522
|
+
const tenantId = resolveTenantId3(client, options?.tenantId);
|
|
1523
|
+
return await client.get(
|
|
1524
|
+
`/dashboards/${id}`,
|
|
1525
|
+
tenantId,
|
|
1526
|
+
options?.userId,
|
|
1527
|
+
options?.scopes,
|
|
1528
|
+
signal
|
|
1529
|
+
);
|
|
1530
|
+
}
|
|
1531
|
+
async function getDashboardForTenant(client, id, tenantId, options, signal) {
|
|
1532
|
+
const params = new URLSearchParams();
|
|
1533
|
+
params.set("tenant_id", tenantId);
|
|
1534
|
+
return await client.get(
|
|
1535
|
+
`/dashboards/${id}/for-tenant?${params.toString()}`,
|
|
1536
|
+
tenantId,
|
|
1537
|
+
options?.userId,
|
|
1538
|
+
options?.scopes,
|
|
1539
|
+
signal
|
|
1540
|
+
);
|
|
1541
|
+
}
|
|
1542
|
+
async function updateDashboard(client, id, body, options, signal) {
|
|
1543
|
+
const tenantId = resolveTenantId3(client, options?.tenantId);
|
|
1544
|
+
return await client.put(
|
|
1545
|
+
`/dashboards/${id}`,
|
|
1546
|
+
body,
|
|
1547
|
+
tenantId,
|
|
1548
|
+
options?.userId,
|
|
1549
|
+
options?.scopes,
|
|
1550
|
+
signal
|
|
1551
|
+
);
|
|
1552
|
+
}
|
|
1553
|
+
async function updateDashboardStatus(client, id, status, options, signal) {
|
|
1554
|
+
const tenantId = resolveTenantId3(client, options?.tenantId);
|
|
1555
|
+
return await client.patch(
|
|
1556
|
+
`/dashboards/${id}/status`,
|
|
1557
|
+
{ status },
|
|
1558
|
+
tenantId,
|
|
1559
|
+
options?.userId,
|
|
1560
|
+
options?.scopes,
|
|
1561
|
+
signal
|
|
1562
|
+
);
|
|
1563
|
+
}
|
|
1564
|
+
async function deleteDashboard(client, id, options, signal) {
|
|
1565
|
+
const tenantId = resolveTenantId3(client, options?.tenantId);
|
|
1566
|
+
await client.delete(
|
|
1567
|
+
`/dashboards/${id}`,
|
|
1568
|
+
tenantId,
|
|
1569
|
+
options?.userId,
|
|
1570
|
+
options?.scopes,
|
|
1571
|
+
signal
|
|
1572
|
+
);
|
|
1573
|
+
}
|
|
1574
|
+
async function forkDashboard(client, id, input, options, signal) {
|
|
1575
|
+
const tenantId = resolveTenantId3(client, options?.tenantId);
|
|
1576
|
+
return await client.post(
|
|
1577
|
+
`/dashboards/${id}/fork`,
|
|
1578
|
+
input,
|
|
1579
|
+
tenantId,
|
|
1580
|
+
options?.userId,
|
|
1581
|
+
options?.scopes,
|
|
1582
|
+
signal
|
|
1583
|
+
);
|
|
1584
|
+
}
|
|
1585
|
+
async function updateFork(client, forkId, input, options, signal) {
|
|
1586
|
+
const tenantId = resolveTenantId3(client, options?.tenantId);
|
|
1587
|
+
return await client.put(
|
|
1588
|
+
`/dashboards/forks/${forkId}`,
|
|
1589
|
+
input,
|
|
1590
|
+
tenantId,
|
|
1591
|
+
options?.userId,
|
|
1592
|
+
options?.scopes,
|
|
1593
|
+
signal
|
|
1594
|
+
);
|
|
1595
|
+
}
|
|
1596
|
+
async function rollbackFork(client, forkId, tenantId, options, signal) {
|
|
1597
|
+
return await client.post(
|
|
1598
|
+
`/dashboards/forks/${forkId}/rollback`,
|
|
1599
|
+
{ tenant_id: tenantId },
|
|
1600
|
+
tenantId,
|
|
1601
|
+
options?.userId,
|
|
1602
|
+
options?.scopes,
|
|
1603
|
+
signal
|
|
1604
|
+
);
|
|
1605
|
+
}
|
|
1606
|
+
async function deleteFork(client, forkId, tenantId, options, signal) {
|
|
1607
|
+
const params = new URLSearchParams();
|
|
1608
|
+
params.set("tenant_id", tenantId);
|
|
1609
|
+
await client.delete(
|
|
1610
|
+
`/dashboards/forks/${forkId}?${params.toString()}`,
|
|
1611
|
+
tenantId,
|
|
1612
|
+
options?.userId,
|
|
1613
|
+
options?.scopes,
|
|
1614
|
+
signal
|
|
1615
|
+
);
|
|
1616
|
+
}
|
|
1617
|
+
async function listForksForTenant(client, tenantId, options, signal) {
|
|
1618
|
+
return await client.get(
|
|
1619
|
+
`/dashboards/customer/${tenantId}`,
|
|
1620
|
+
tenantId,
|
|
1621
|
+
options?.userId,
|
|
1622
|
+
options?.scopes,
|
|
1623
|
+
signal
|
|
1624
|
+
);
|
|
1625
|
+
}
|
|
1171
1626
|
|
|
1172
1627
|
// src/routes/ingest.ts
|
|
1173
1628
|
import crypto2 from "crypto";
|
|
1174
1629
|
async function syncSchema(client, queryEngine, databaseName, options, signal) {
|
|
1175
|
-
const tenantId =
|
|
1630
|
+
const tenantId = resolveTenantId4(client, options.tenantId);
|
|
1176
1631
|
const adapter = queryEngine.getDatabase(databaseName);
|
|
1177
1632
|
const metadata = queryEngine.getDatabaseMetadata(databaseName);
|
|
1178
1633
|
const introspection = await adapter.introspect(
|
|
@@ -1194,7 +1649,7 @@ async function syncSchema(client, queryEngine, databaseName, options, signal) {
|
|
|
1194
1649
|
);
|
|
1195
1650
|
return response;
|
|
1196
1651
|
}
|
|
1197
|
-
function
|
|
1652
|
+
function resolveTenantId4(client, tenantId) {
|
|
1198
1653
|
const resolved = tenantId ?? client.getDefaultTenantId();
|
|
1199
1654
|
if (!resolved) {
|
|
1200
1655
|
throw new Error(
|
|
@@ -1236,7 +1691,7 @@ import crypto4 from "crypto";
|
|
|
1236
1691
|
// src/routes/query.ts
|
|
1237
1692
|
import crypto3 from "crypto";
|
|
1238
1693
|
async function ask(client, queryEngine, question, options, signal) {
|
|
1239
|
-
const tenantId =
|
|
1694
|
+
const tenantId = resolveTenantId5(client, options.tenantId);
|
|
1240
1695
|
const sessionId = crypto3.randomUUID();
|
|
1241
1696
|
const querypanelSessionId = options.querypanelSessionId ?? sessionId;
|
|
1242
1697
|
const maxRetry = options.maxRetry ?? 0;
|
|
@@ -1266,7 +1721,8 @@ async function ask(client, queryEngine, question, options, signal) {
|
|
|
1266
1721
|
...options.maxRetry ? { max_retry: options.maxRetry } : {},
|
|
1267
1722
|
...tenantSettings ? { tenant_settings: tenantSettings } : {},
|
|
1268
1723
|
...databaseName ? { database: databaseName } : {},
|
|
1269
|
-
...metadata?.dialect ? { dialect: metadata.dialect } : {}
|
|
1724
|
+
...metadata?.dialect ? { dialect: metadata.dialect } : {},
|
|
1725
|
+
...options.model?.trim() ? { model: options.model.trim() } : {}
|
|
1270
1726
|
},
|
|
1271
1727
|
tenantId,
|
|
1272
1728
|
options.userId,
|
|
@@ -1290,7 +1746,7 @@ async function ask(client, queryEngine, question, options, signal) {
|
|
|
1290
1746
|
const dbName = queryResponse.data.database ?? options.database ?? queryEngine.getDefaultDatabase();
|
|
1291
1747
|
if (!dbName) {
|
|
1292
1748
|
throw new Error(
|
|
1293
|
-
"No database attached. Call attachPostgres
|
|
1749
|
+
"No database attached. Call attachPostgres, attachClickhouse, or attachBigQuery first."
|
|
1294
1750
|
);
|
|
1295
1751
|
}
|
|
1296
1752
|
const paramMetadata = Array.isArray(queryResponse.data.params) ? queryResponse.data.params : [];
|
|
@@ -1391,7 +1847,7 @@ async function ask(client, queryEngine, question, options, signal) {
|
|
|
1391
1847
|
}
|
|
1392
1848
|
throw new Error("Unexpected error in ask retry loop");
|
|
1393
1849
|
}
|
|
1394
|
-
function
|
|
1850
|
+
function resolveTenantId5(client, tenantId) {
|
|
1395
1851
|
const resolved = tenantId ?? client.getDefaultTenantId();
|
|
1396
1852
|
if (!resolved) {
|
|
1397
1853
|
throw new Error(
|
|
@@ -1547,7 +2003,7 @@ function stripVizSpecOnlyHints(hints) {
|
|
|
1547
2003
|
const { kind: _kind, ...rest } = hints;
|
|
1548
2004
|
return rest;
|
|
1549
2005
|
}
|
|
1550
|
-
function
|
|
2006
|
+
function resolveTenantId6(client, tenantId) {
|
|
1551
2007
|
const resolved = tenantId ?? client.getDefaultTenantId();
|
|
1552
2008
|
if (!resolved) {
|
|
1553
2009
|
throw new Error(
|
|
@@ -1557,7 +2013,7 @@ function resolveTenantId5(client, tenantId) {
|
|
|
1557
2013
|
return resolved;
|
|
1558
2014
|
}
|
|
1559
2015
|
async function modifyChart(client, queryEngine, input, options, signal) {
|
|
1560
|
-
const tenantId =
|
|
2016
|
+
const tenantId = resolveTenantId6(client, options?.tenantId);
|
|
1561
2017
|
const sessionId = crypto4.randomUUID();
|
|
1562
2018
|
const querypanelSessionId = options?.querypanelSessionId ?? sessionId;
|
|
1563
2019
|
const hasQuerypanelSessionContext = !!options?.querypanelSessionId;
|
|
@@ -1725,7 +2181,7 @@ async function modifyChart(client, queryEngine, input, options, signal) {
|
|
|
1725
2181
|
|
|
1726
2182
|
// src/routes/sessions.ts
|
|
1727
2183
|
async function listSessions(client, options, signal) {
|
|
1728
|
-
const tenantId =
|
|
2184
|
+
const tenantId = resolveTenantId7(client, options?.tenantId);
|
|
1729
2185
|
const params = new URLSearchParams();
|
|
1730
2186
|
if (options?.pagination?.page)
|
|
1731
2187
|
params.set("page", `${options.pagination.page}`);
|
|
@@ -1748,7 +2204,7 @@ async function listSessions(client, options, signal) {
|
|
|
1748
2204
|
);
|
|
1749
2205
|
}
|
|
1750
2206
|
async function getSession(client, sessionId, options, signal) {
|
|
1751
|
-
const tenantId =
|
|
2207
|
+
const tenantId = resolveTenantId7(client, options?.tenantId);
|
|
1752
2208
|
const params = new URLSearchParams();
|
|
1753
2209
|
if (options?.includeTurns !== void 0) {
|
|
1754
2210
|
params.set("include_turns", `${options.includeTurns}`);
|
|
@@ -1762,7 +2218,7 @@ async function getSession(client, sessionId, options, signal) {
|
|
|
1762
2218
|
);
|
|
1763
2219
|
}
|
|
1764
2220
|
async function updateSession(client, sessionId, body, options, signal) {
|
|
1765
|
-
const tenantId =
|
|
2221
|
+
const tenantId = resolveTenantId7(client, options?.tenantId);
|
|
1766
2222
|
return await client.patch(
|
|
1767
2223
|
`/sessions/${encodeURIComponent(sessionId)}`,
|
|
1768
2224
|
body,
|
|
@@ -1773,7 +2229,7 @@ async function updateSession(client, sessionId, body, options, signal) {
|
|
|
1773
2229
|
);
|
|
1774
2230
|
}
|
|
1775
2231
|
async function deleteSession(client, sessionId, options, signal) {
|
|
1776
|
-
const tenantId =
|
|
2232
|
+
const tenantId = resolveTenantId7(client, options?.tenantId);
|
|
1777
2233
|
await client.delete(
|
|
1778
2234
|
`/sessions/${encodeURIComponent(sessionId)}`,
|
|
1779
2235
|
tenantId,
|
|
@@ -1782,7 +2238,7 @@ async function deleteSession(client, sessionId, options, signal) {
|
|
|
1782
2238
|
signal
|
|
1783
2239
|
);
|
|
1784
2240
|
}
|
|
1785
|
-
function
|
|
2241
|
+
function resolveTenantId7(client, tenantId) {
|
|
1786
2242
|
const resolved = tenantId ?? client.getDefaultTenantId();
|
|
1787
2243
|
if (!resolved) {
|
|
1788
2244
|
throw new Error(
|
|
@@ -1795,7 +2251,7 @@ function resolveTenantId6(client, tenantId) {
|
|
|
1795
2251
|
// src/routes/vizspec.ts
|
|
1796
2252
|
import crypto5 from "crypto";
|
|
1797
2253
|
async function generateVizSpec(client, input, options, signal) {
|
|
1798
|
-
const tenantId =
|
|
2254
|
+
const tenantId = resolveTenantId8(client, options?.tenantId);
|
|
1799
2255
|
const sessionId = crypto5.randomUUID();
|
|
1800
2256
|
const response = await client.post(
|
|
1801
2257
|
"/vizspec",
|
|
@@ -1817,7 +2273,7 @@ async function generateVizSpec(client, input, options, signal) {
|
|
|
1817
2273
|
);
|
|
1818
2274
|
return response;
|
|
1819
2275
|
}
|
|
1820
|
-
function
|
|
2276
|
+
function resolveTenantId8(client, tenantId) {
|
|
1821
2277
|
const resolved = tenantId ?? client.getDefaultTenantId();
|
|
1822
2278
|
if (!resolved) {
|
|
1823
2279
|
throw new Error(
|
|
@@ -1828,13 +2284,64 @@ function resolveTenantId7(client, tenantId) {
|
|
|
1828
2284
|
}
|
|
1829
2285
|
|
|
1830
2286
|
// src/index.ts
|
|
1831
|
-
var QueryPanelSdkAPI = class {
|
|
2287
|
+
var QueryPanelSdkAPI = class _QueryPanelSdkAPI {
|
|
1832
2288
|
client;
|
|
1833
2289
|
queryEngine;
|
|
1834
|
-
|
|
1835
|
-
|
|
2290
|
+
/**
|
|
2291
|
+
* @param workspaceId - Workspace UUID (same value as your org in the dashboard). Passed to the API as JWT claim `organizationId`.
|
|
2292
|
+
*/
|
|
2293
|
+
constructor(baseUrl, privateKey, workspaceId, options) {
|
|
2294
|
+
if (options?.api) {
|
|
2295
|
+
this.client = options.api;
|
|
2296
|
+
} else {
|
|
2297
|
+
if (!baseUrl) throw new Error("Base URL is required");
|
|
2298
|
+
if (!privateKey) throw new Error("Private key is required");
|
|
2299
|
+
if (!workspaceId) throw new Error("Workspace ID is required");
|
|
2300
|
+
this.client = new ApiClient(baseUrl, privateKey, workspaceId, {
|
|
2301
|
+
defaultTenantId: options?.defaultTenantId,
|
|
2302
|
+
additionalHeaders: options?.additionalHeaders,
|
|
2303
|
+
fetch: options?.fetch
|
|
2304
|
+
});
|
|
2305
|
+
}
|
|
1836
2306
|
this.queryEngine = new QueryEngine();
|
|
1837
2307
|
}
|
|
2308
|
+
/**
|
|
2309
|
+
* Create an SDK instance that uses a request callback instead of HTTP.
|
|
2310
|
+
* Use this when calling the SDK from within your own API so that "API" calls
|
|
2311
|
+
* are handled in-process (e.g. by invoking querypanel-sdk services directly)
|
|
2312
|
+
* and do not cause recursion.
|
|
2313
|
+
*
|
|
2314
|
+
* @param workspaceId - Workspace identifier (sent as `organizationId` in JWTs and API auth)
|
|
2315
|
+
* @param requestHandler - Callback invoked for each logical API request (method, path, body, tenantId, etc.)
|
|
2316
|
+
* @param options - Optional defaultTenantId
|
|
2317
|
+
* @returns QueryPanelSdkAPI instance; attach databases and call ask(), syncSchema(), etc. as usual
|
|
2318
|
+
*
|
|
2319
|
+
* @example
|
|
2320
|
+
* ```ts
|
|
2321
|
+
* const qp = QueryPanelSdkAPI.withCallbacks(
|
|
2322
|
+
* process.env.QUERYPANEL_WORKSPACE_ID!,
|
|
2323
|
+
* async (opts) => {
|
|
2324
|
+
* // Call your in-process services (e.g. querypanel-sdk logic) instead of HTTP
|
|
2325
|
+
* if (opts.path === '/query' || opts.path === '/v2/query') {
|
|
2326
|
+
* const result = await yourSqlGenerator.generate(opts.body, opts.tenantId);
|
|
2327
|
+
* return { data: result, headers: new Headers({ 'x-querypanel-session-id': result.sessionId }) };
|
|
2328
|
+
* }
|
|
2329
|
+
* if (opts.path === '/chart') return { data: await yourChartService.generate(opts.body) };
|
|
2330
|
+
* // ... other paths
|
|
2331
|
+
* return { data: await yourProxy(opts) };
|
|
2332
|
+
* },
|
|
2333
|
+
* { defaultTenantId: process.env.DEFAULT_TENANT_ID }
|
|
2334
|
+
* );
|
|
2335
|
+
* qp.attachPostgres('db', createClient, { tenantFieldName: 'tenant_id' });
|
|
2336
|
+
* const result = await qp.ask('Top 10 orders', { tenantId: 't1', database: 'db' });
|
|
2337
|
+
* ```
|
|
2338
|
+
*/
|
|
2339
|
+
static withCallbacks(workspaceId, requestHandler, options) {
|
|
2340
|
+
const api = new CallbackApiClient(requestHandler, {
|
|
2341
|
+
defaultTenantId: options?.defaultTenantId
|
|
2342
|
+
});
|
|
2343
|
+
return new _QueryPanelSdkAPI("", "", workspaceId, { api });
|
|
2344
|
+
}
|
|
1838
2345
|
// Database attachment methods
|
|
1839
2346
|
attachClickhouse(name, clientFn, options) {
|
|
1840
2347
|
const adapter = new ClickHouseAdapter(clientFn, options);
|
|
@@ -1862,6 +2369,19 @@ var QueryPanelSdkAPI = class {
|
|
|
1862
2369
|
};
|
|
1863
2370
|
this.queryEngine.attachDatabase(name, adapter, metadata);
|
|
1864
2371
|
}
|
|
2372
|
+
attachBigQuery(name, clientFn, options) {
|
|
2373
|
+
const adapter = new BigQueryAdapter(clientFn, options);
|
|
2374
|
+
const metadata = {
|
|
2375
|
+
name,
|
|
2376
|
+
dialect: "bigquery",
|
|
2377
|
+
description: options?.description,
|
|
2378
|
+
tags: options?.tags,
|
|
2379
|
+
tenantFieldName: options?.tenantFieldName,
|
|
2380
|
+
tenantFieldType: options?.tenantFieldType ?? "String",
|
|
2381
|
+
enforceTenantIsolation: options?.tenantFieldName ? options?.enforceTenantIsolation ?? true : void 0
|
|
2382
|
+
};
|
|
2383
|
+
this.queryEngine.attachDatabase(name, adapter, metadata);
|
|
2384
|
+
}
|
|
1865
2385
|
attachDatabase(name, adapter) {
|
|
1866
2386
|
const metadata = {
|
|
1867
2387
|
name,
|
|
@@ -1948,6 +2468,48 @@ var QueryPanelSdkAPI = class {
|
|
|
1948
2468
|
signal
|
|
1949
2469
|
);
|
|
1950
2470
|
}
|
|
2471
|
+
// Embedded dashboard / JWT
|
|
2472
|
+
/**
|
|
2473
|
+
* Creates a JWT for the given tenant (and optional userId, scopes).
|
|
2474
|
+
* Use this when you need to pass a token to the embed (e.g. frontend or demo).
|
|
2475
|
+
* Only available when using the HTTP client (ApiClient); not available when using withCallbacks.
|
|
2476
|
+
*/
|
|
2477
|
+
async createJwt(options) {
|
|
2478
|
+
const tenantId = options.tenantId?.trim();
|
|
2479
|
+
if (!tenantId) {
|
|
2480
|
+
throw new Error("tenantId is required");
|
|
2481
|
+
}
|
|
2482
|
+
if (!(this.client instanceof ApiClient)) {
|
|
2483
|
+
throw new Error(
|
|
2484
|
+
"createJwt is not available when using withCallbacks. Use the SDK with baseUrl + privateKey to create JWTs."
|
|
2485
|
+
);
|
|
2486
|
+
}
|
|
2487
|
+
return await this.client.createJwt(
|
|
2488
|
+
tenantId,
|
|
2489
|
+
options.userId,
|
|
2490
|
+
options.scopes
|
|
2491
|
+
);
|
|
2492
|
+
}
|
|
2493
|
+
/**
|
|
2494
|
+
* Runs SQL against an attached database with tenant isolation applied.
|
|
2495
|
+
* Used by embed run-sql (e.g. dashboard chart execution). Requires a database
|
|
2496
|
+
* to be attached (e.g. via attachPostgres) and options.tenantId.
|
|
2497
|
+
*/
|
|
2498
|
+
async runSqlForDashboard(input, options) {
|
|
2499
|
+
const tenantId = options.tenantId?.trim();
|
|
2500
|
+
if (!tenantId) {
|
|
2501
|
+
throw new Error("tenantId is required");
|
|
2502
|
+
}
|
|
2503
|
+
const databaseName = input.database ?? "db";
|
|
2504
|
+
const params = input.params ?? {};
|
|
2505
|
+
const result = await this.queryEngine.validateAndExecute(
|
|
2506
|
+
input.sql,
|
|
2507
|
+
params,
|
|
2508
|
+
databaseName,
|
|
2509
|
+
tenantId
|
|
2510
|
+
);
|
|
2511
|
+
return { rows: result.rows, fields: result.fields };
|
|
2512
|
+
}
|
|
1951
2513
|
// VizSpec generation
|
|
1952
2514
|
/**
|
|
1953
2515
|
* Generates a VizSpec visualization specification from query results.
|
|
@@ -2391,8 +2953,297 @@ var QueryPanelSdkAPI = class {
|
|
|
2391
2953
|
async deleteActiveChart(id, options, signal) {
|
|
2392
2954
|
await deleteActiveChart(this.client, id, options, signal);
|
|
2393
2955
|
}
|
|
2956
|
+
// Dashboard CRUD operations
|
|
2957
|
+
/**
|
|
2958
|
+
* Creates a new dashboard with BlockNote content.
|
|
2959
|
+
*
|
|
2960
|
+
* @param body - Dashboard configuration including name and BlockNote content
|
|
2961
|
+
* @param options - Tenant, user, and scope options
|
|
2962
|
+
* @param signal - Optional AbortSignal for cancellation
|
|
2963
|
+
* @returns Created dashboard
|
|
2964
|
+
*
|
|
2965
|
+
* @example
|
|
2966
|
+
* ```typescript
|
|
2967
|
+
* const dashboard = await qp.createDashboard({
|
|
2968
|
+
* name: "Sales Dashboard",
|
|
2969
|
+
* description: "Monthly sales metrics",
|
|
2970
|
+
* content_json: blockNoteContent,
|
|
2971
|
+
* editor_type: "blocknote",
|
|
2972
|
+
* }, { tenantId: "tenant_123" });
|
|
2973
|
+
* ```
|
|
2974
|
+
*/
|
|
2975
|
+
async createDashboard(body, options, signal) {
|
|
2976
|
+
return await createDashboard(
|
|
2977
|
+
this.client,
|
|
2978
|
+
body,
|
|
2979
|
+
options,
|
|
2980
|
+
signal
|
|
2981
|
+
);
|
|
2982
|
+
}
|
|
2983
|
+
/**
|
|
2984
|
+
* Lists dashboards with pagination and filtering.
|
|
2985
|
+
*
|
|
2986
|
+
* @param options - Filtering and pagination options
|
|
2987
|
+
* @param signal - Optional AbortSignal for cancellation
|
|
2988
|
+
* @returns Paginated list of dashboards
|
|
2989
|
+
*
|
|
2990
|
+
* @example
|
|
2991
|
+
* ```typescript
|
|
2992
|
+
* const dashboards = await qp.listDashboards({
|
|
2993
|
+
* tenantId: "tenant_123",
|
|
2994
|
+
* status: "deployed",
|
|
2995
|
+
* pagination: { page: 1, limit: 10 },
|
|
2996
|
+
* });
|
|
2997
|
+
* ```
|
|
2998
|
+
*/
|
|
2999
|
+
async listDashboards(options, signal) {
|
|
3000
|
+
return await listDashboards(this.client, options, signal);
|
|
3001
|
+
}
|
|
3002
|
+
/**
|
|
3003
|
+
* Gets a dashboard by ID.
|
|
3004
|
+
*
|
|
3005
|
+
* @param id - Dashboard ID
|
|
3006
|
+
* @param options - Tenant, user, and scope options
|
|
3007
|
+
* @param signal - Optional AbortSignal for cancellation
|
|
3008
|
+
* @returns Dashboard details
|
|
3009
|
+
*
|
|
3010
|
+
* @example
|
|
3011
|
+
* ```typescript
|
|
3012
|
+
* const dashboard = await qp.getDashboard("dash_123", {
|
|
3013
|
+
* tenantId: "tenant_123",
|
|
3014
|
+
* });
|
|
3015
|
+
* ```
|
|
3016
|
+
*/
|
|
3017
|
+
async getDashboard(id, options, signal) {
|
|
3018
|
+
return await getDashboard(
|
|
3019
|
+
this.client,
|
|
3020
|
+
id,
|
|
3021
|
+
options,
|
|
3022
|
+
signal
|
|
3023
|
+
);
|
|
3024
|
+
}
|
|
3025
|
+
/**
|
|
3026
|
+
* Gets a dashboard for a specific tenant.
|
|
3027
|
+
* Returns customer fork if exists, otherwise returns the original dashboard.
|
|
3028
|
+
*
|
|
3029
|
+
* @param id - Dashboard ID
|
|
3030
|
+
* @param tenantId - Tenant ID to check for fork
|
|
3031
|
+
* @param options - Additional auth options
|
|
3032
|
+
* @param signal - Optional AbortSignal for cancellation
|
|
3033
|
+
* @returns Dashboard (fork if exists, original otherwise)
|
|
3034
|
+
*
|
|
3035
|
+
* @example
|
|
3036
|
+
* ```typescript
|
|
3037
|
+
* const dashboard = await qp.getDashboardForTenant(
|
|
3038
|
+
* "dash_123",
|
|
3039
|
+
* "tenant_456",
|
|
3040
|
+
* );
|
|
3041
|
+
* ```
|
|
3042
|
+
*/
|
|
3043
|
+
async getDashboardForTenant(id, tenantId, options, signal) {
|
|
3044
|
+
return await getDashboardForTenant(
|
|
3045
|
+
this.client,
|
|
3046
|
+
id,
|
|
3047
|
+
tenantId,
|
|
3048
|
+
options,
|
|
3049
|
+
signal
|
|
3050
|
+
);
|
|
3051
|
+
}
|
|
3052
|
+
/**
|
|
3053
|
+
* Updates a dashboard.
|
|
3054
|
+
*
|
|
3055
|
+
* @param id - Dashboard ID to update
|
|
3056
|
+
* @param body - Fields to update
|
|
3057
|
+
* @param options - Tenant, user, and scope options
|
|
3058
|
+
* @param signal - Optional AbortSignal for cancellation
|
|
3059
|
+
* @returns Updated dashboard
|
|
3060
|
+
*
|
|
3061
|
+
* @example
|
|
3062
|
+
* ```typescript
|
|
3063
|
+
* const updated = await qp.updateDashboard("dash_123", {
|
|
3064
|
+
* name: "Updated Dashboard",
|
|
3065
|
+
* content_json: newBlockNoteContent,
|
|
3066
|
+
* }, { tenantId: "tenant_123" });
|
|
3067
|
+
* ```
|
|
3068
|
+
*/
|
|
3069
|
+
async updateDashboard(id, body, options, signal) {
|
|
3070
|
+
return await updateDashboard(
|
|
3071
|
+
this.client,
|
|
3072
|
+
id,
|
|
3073
|
+
body,
|
|
3074
|
+
options,
|
|
3075
|
+
signal
|
|
3076
|
+
);
|
|
3077
|
+
}
|
|
3078
|
+
/**
|
|
3079
|
+
* Updates dashboard status (deploy/undeploy).
|
|
3080
|
+
*
|
|
3081
|
+
* @param id - Dashboard ID
|
|
3082
|
+
* @param status - New status
|
|
3083
|
+
* @param options - Tenant, user, and scope options
|
|
3084
|
+
* @param signal - Optional AbortSignal for cancellation
|
|
3085
|
+
* @returns Updated dashboard
|
|
3086
|
+
*
|
|
3087
|
+
* @example
|
|
3088
|
+
* ```typescript
|
|
3089
|
+
* const deployed = await qp.updateDashboardStatus(
|
|
3090
|
+
* "dash_123",
|
|
3091
|
+
* "deployed",
|
|
3092
|
+
* { tenantId: "tenant_123" },
|
|
3093
|
+
* );
|
|
3094
|
+
* ```
|
|
3095
|
+
*/
|
|
3096
|
+
async updateDashboardStatus(id, status, options, signal) {
|
|
3097
|
+
return await updateDashboardStatus(
|
|
3098
|
+
this.client,
|
|
3099
|
+
id,
|
|
3100
|
+
status,
|
|
3101
|
+
options,
|
|
3102
|
+
signal
|
|
3103
|
+
);
|
|
3104
|
+
}
|
|
3105
|
+
/**
|
|
3106
|
+
* Deletes a dashboard.
|
|
3107
|
+
*
|
|
3108
|
+
* @param id - Dashboard ID to delete
|
|
3109
|
+
* @param options - Tenant, user, and scope options
|
|
3110
|
+
* @param signal - Optional AbortSignal for cancellation
|
|
3111
|
+
*
|
|
3112
|
+
* @example
|
|
3113
|
+
* ```typescript
|
|
3114
|
+
* await qp.deleteDashboard("dash_123", { tenantId: "tenant_123" });
|
|
3115
|
+
* ```
|
|
3116
|
+
*/
|
|
3117
|
+
async deleteDashboard(id, options, signal) {
|
|
3118
|
+
await deleteDashboard(this.client, id, options, signal);
|
|
3119
|
+
}
|
|
3120
|
+
// Dashboard Fork operations (Customer Customization)
|
|
3121
|
+
/**
|
|
3122
|
+
* Forks a dashboard for customer customization (copy-on-write).
|
|
3123
|
+
*
|
|
3124
|
+
* Creates a full copy of the dashboard that the customer can edit independently.
|
|
3125
|
+
* The original dashboard remains unchanged.
|
|
3126
|
+
*
|
|
3127
|
+
* @param id - Dashboard ID to fork
|
|
3128
|
+
* @param input - Fork configuration with tenant_id
|
|
3129
|
+
* @param options - Additional auth options
|
|
3130
|
+
* @param signal - Optional AbortSignal for cancellation
|
|
3131
|
+
* @returns Forked dashboard
|
|
3132
|
+
*
|
|
3133
|
+
* @example
|
|
3134
|
+
* ```typescript
|
|
3135
|
+
* const fork = await qp.forkDashboard("dash_123", {
|
|
3136
|
+
* tenant_id: "tenant_456",
|
|
3137
|
+
* name: "Customer's Custom Dashboard",
|
|
3138
|
+
* });
|
|
3139
|
+
* ```
|
|
3140
|
+
*/
|
|
3141
|
+
async forkDashboard(id, input, options, signal) {
|
|
3142
|
+
return await forkDashboard(
|
|
3143
|
+
this.client,
|
|
3144
|
+
id,
|
|
3145
|
+
input,
|
|
3146
|
+
options,
|
|
3147
|
+
signal
|
|
3148
|
+
);
|
|
3149
|
+
}
|
|
3150
|
+
/**
|
|
3151
|
+
* Updates a customer fork.
|
|
3152
|
+
*
|
|
3153
|
+
* @param forkId - Fork ID to update
|
|
3154
|
+
* @param input - Update data including tenant_id
|
|
3155
|
+
* @param options - Additional auth options
|
|
3156
|
+
* @param signal - Optional AbortSignal for cancellation
|
|
3157
|
+
* @returns Updated fork
|
|
3158
|
+
*
|
|
3159
|
+
* @example
|
|
3160
|
+
* ```typescript
|
|
3161
|
+
* const updated = await qp.updateFork("fork_123", {
|
|
3162
|
+
* tenant_id: "tenant_456",
|
|
3163
|
+
* content_json: newBlockNoteContent,
|
|
3164
|
+
* });
|
|
3165
|
+
* ```
|
|
3166
|
+
*/
|
|
3167
|
+
async updateFork(forkId, input, options, signal) {
|
|
3168
|
+
return await updateFork(
|
|
3169
|
+
this.client,
|
|
3170
|
+
forkId,
|
|
3171
|
+
input,
|
|
3172
|
+
options,
|
|
3173
|
+
signal
|
|
3174
|
+
);
|
|
3175
|
+
}
|
|
3176
|
+
/**
|
|
3177
|
+
* Rollbacks a fork to the original dashboard.
|
|
3178
|
+
* This deletes the fork and the customer will see the original again.
|
|
3179
|
+
*
|
|
3180
|
+
* @param forkId - Fork ID to rollback
|
|
3181
|
+
* @param tenantId - Tenant ID
|
|
3182
|
+
* @param options - Additional auth options
|
|
3183
|
+
* @param signal - Optional AbortSignal for cancellation
|
|
3184
|
+
* @returns Original dashboard
|
|
3185
|
+
*
|
|
3186
|
+
* @example
|
|
3187
|
+
* ```typescript
|
|
3188
|
+
* const original = await qp.rollbackFork("fork_123", "tenant_456");
|
|
3189
|
+
* ```
|
|
3190
|
+
*/
|
|
3191
|
+
async rollbackFork(forkId, tenantId, options, signal) {
|
|
3192
|
+
return await rollbackFork(
|
|
3193
|
+
this.client,
|
|
3194
|
+
forkId,
|
|
3195
|
+
tenantId,
|
|
3196
|
+
options,
|
|
3197
|
+
signal
|
|
3198
|
+
);
|
|
3199
|
+
}
|
|
3200
|
+
/**
|
|
3201
|
+
* Deletes a customer fork.
|
|
3202
|
+
*
|
|
3203
|
+
* @param forkId - Fork ID to delete
|
|
3204
|
+
* @param tenantId - Tenant ID
|
|
3205
|
+
* @param options - Additional auth options
|
|
3206
|
+
* @param signal - Optional AbortSignal for cancellation
|
|
3207
|
+
*
|
|
3208
|
+
* @example
|
|
3209
|
+
* ```typescript
|
|
3210
|
+
* await qp.deleteFork("fork_123", "tenant_456");
|
|
3211
|
+
* ```
|
|
3212
|
+
*/
|
|
3213
|
+
async deleteFork(forkId, tenantId, options, signal) {
|
|
3214
|
+
await deleteFork(
|
|
3215
|
+
this.client,
|
|
3216
|
+
forkId,
|
|
3217
|
+
tenantId,
|
|
3218
|
+
options,
|
|
3219
|
+
signal
|
|
3220
|
+
);
|
|
3221
|
+
}
|
|
3222
|
+
/**
|
|
3223
|
+
* Lists all customer forks for a tenant.
|
|
3224
|
+
*
|
|
3225
|
+
* @param tenantId - Tenant ID
|
|
3226
|
+
* @param options - Additional auth options
|
|
3227
|
+
* @param signal - Optional AbortSignal for cancellation
|
|
3228
|
+
* @returns Array of forks
|
|
3229
|
+
*
|
|
3230
|
+
* @example
|
|
3231
|
+
* ```typescript
|
|
3232
|
+
* const forks = await qp.listForksForTenant("tenant_456");
|
|
3233
|
+
* ```
|
|
3234
|
+
*/
|
|
3235
|
+
async listForksForTenant(tenantId, options, signal) {
|
|
3236
|
+
return await listForksForTenant(
|
|
3237
|
+
this.client,
|
|
3238
|
+
tenantId,
|
|
3239
|
+
options,
|
|
3240
|
+
signal
|
|
3241
|
+
);
|
|
3242
|
+
}
|
|
2394
3243
|
};
|
|
2395
3244
|
export {
|
|
3245
|
+
BigQueryAdapter,
|
|
3246
|
+
CallbackApiClient,
|
|
2396
3247
|
ClickHouseAdapter,
|
|
2397
3248
|
PostgresAdapter,
|
|
2398
3249
|
QueryErrorCode,
|