@querypanel/node-sdk 1.0.56 → 1.2.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/README.md +2 -2
- package/dist/index.cjs +919 -67
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +530 -9
- package/dist/index.d.ts +530 -9
- package/dist/index.js +917 -67
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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
|
|
530
|
+
return positionalParams;
|
|
346
531
|
}
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
positionalParams.push(val);
|
|
350
|
-
}
|
|
351
|
-
return positionalParams;
|
|
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}'`;
|
|
@@ -1149,30 +1453,181 @@ async function updateActiveChart(client, id, body, options, signal) {
|
|
|
1149
1453
|
signal
|
|
1150
1454
|
);
|
|
1151
1455
|
}
|
|
1152
|
-
async function deleteActiveChart(client, id, options, signal) {
|
|
1153
|
-
const tenantId = resolveTenantId2(client, options?.tenantId);
|
|
1154
|
-
await client.delete(
|
|
1155
|
-
`/active-charts/${encodeURIComponent(id)}`,
|
|
1456
|
+
async function deleteActiveChart(client, id, options, signal) {
|
|
1457
|
+
const tenantId = resolveTenantId2(client, options?.tenantId);
|
|
1458
|
+
await client.delete(
|
|
1459
|
+
`/active-charts/${encodeURIComponent(id)}`,
|
|
1460
|
+
tenantId,
|
|
1461
|
+
options?.userId,
|
|
1462
|
+
options?.scopes,
|
|
1463
|
+
signal
|
|
1464
|
+
);
|
|
1465
|
+
}
|
|
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}`,
|
|
1156
1620
|
tenantId,
|
|
1157
1621
|
options?.userId,
|
|
1158
1622
|
options?.scopes,
|
|
1159
1623
|
signal
|
|
1160
1624
|
);
|
|
1161
1625
|
}
|
|
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
|
-
}
|
|
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;
|
|
@@ -1290,7 +1745,7 @@ async function ask(client, queryEngine, question, options, signal) {
|
|
|
1290
1745
|
const dbName = queryResponse.data.database ?? options.database ?? queryEngine.getDefaultDatabase();
|
|
1291
1746
|
if (!dbName) {
|
|
1292
1747
|
throw new Error(
|
|
1293
|
-
"No database attached. Call attachPostgres
|
|
1748
|
+
"No database attached. Call attachPostgres, attachClickhouse, or attachBigQuery first."
|
|
1294
1749
|
);
|
|
1295
1750
|
}
|
|
1296
1751
|
const paramMetadata = Array.isArray(queryResponse.data.params) ? queryResponse.data.params : [];
|
|
@@ -1391,7 +1846,7 @@ async function ask(client, queryEngine, question, options, signal) {
|
|
|
1391
1846
|
}
|
|
1392
1847
|
throw new Error("Unexpected error in ask retry loop");
|
|
1393
1848
|
}
|
|
1394
|
-
function
|
|
1849
|
+
function resolveTenantId5(client, tenantId) {
|
|
1395
1850
|
const resolved = tenantId ?? client.getDefaultTenantId();
|
|
1396
1851
|
if (!resolved) {
|
|
1397
1852
|
throw new Error(
|
|
@@ -1547,7 +2002,7 @@ function stripVizSpecOnlyHints(hints) {
|
|
|
1547
2002
|
const { kind: _kind, ...rest } = hints;
|
|
1548
2003
|
return rest;
|
|
1549
2004
|
}
|
|
1550
|
-
function
|
|
2005
|
+
function resolveTenantId6(client, tenantId) {
|
|
1551
2006
|
const resolved = tenantId ?? client.getDefaultTenantId();
|
|
1552
2007
|
if (!resolved) {
|
|
1553
2008
|
throw new Error(
|
|
@@ -1557,7 +2012,7 @@ function resolveTenantId5(client, tenantId) {
|
|
|
1557
2012
|
return resolved;
|
|
1558
2013
|
}
|
|
1559
2014
|
async function modifyChart(client, queryEngine, input, options, signal) {
|
|
1560
|
-
const tenantId =
|
|
2015
|
+
const tenantId = resolveTenantId6(client, options?.tenantId);
|
|
1561
2016
|
const sessionId = crypto4.randomUUID();
|
|
1562
2017
|
const querypanelSessionId = options?.querypanelSessionId ?? sessionId;
|
|
1563
2018
|
const hasQuerypanelSessionContext = !!options?.querypanelSessionId;
|
|
@@ -1725,7 +2180,7 @@ async function modifyChart(client, queryEngine, input, options, signal) {
|
|
|
1725
2180
|
|
|
1726
2181
|
// src/routes/sessions.ts
|
|
1727
2182
|
async function listSessions(client, options, signal) {
|
|
1728
|
-
const tenantId =
|
|
2183
|
+
const tenantId = resolveTenantId7(client, options?.tenantId);
|
|
1729
2184
|
const params = new URLSearchParams();
|
|
1730
2185
|
if (options?.pagination?.page)
|
|
1731
2186
|
params.set("page", `${options.pagination.page}`);
|
|
@@ -1748,7 +2203,7 @@ async function listSessions(client, options, signal) {
|
|
|
1748
2203
|
);
|
|
1749
2204
|
}
|
|
1750
2205
|
async function getSession(client, sessionId, options, signal) {
|
|
1751
|
-
const tenantId =
|
|
2206
|
+
const tenantId = resolveTenantId7(client, options?.tenantId);
|
|
1752
2207
|
const params = new URLSearchParams();
|
|
1753
2208
|
if (options?.includeTurns !== void 0) {
|
|
1754
2209
|
params.set("include_turns", `${options.includeTurns}`);
|
|
@@ -1762,7 +2217,7 @@ async function getSession(client, sessionId, options, signal) {
|
|
|
1762
2217
|
);
|
|
1763
2218
|
}
|
|
1764
2219
|
async function updateSession(client, sessionId, body, options, signal) {
|
|
1765
|
-
const tenantId =
|
|
2220
|
+
const tenantId = resolveTenantId7(client, options?.tenantId);
|
|
1766
2221
|
return await client.patch(
|
|
1767
2222
|
`/sessions/${encodeURIComponent(sessionId)}`,
|
|
1768
2223
|
body,
|
|
@@ -1773,7 +2228,7 @@ async function updateSession(client, sessionId, body, options, signal) {
|
|
|
1773
2228
|
);
|
|
1774
2229
|
}
|
|
1775
2230
|
async function deleteSession(client, sessionId, options, signal) {
|
|
1776
|
-
const tenantId =
|
|
2231
|
+
const tenantId = resolveTenantId7(client, options?.tenantId);
|
|
1777
2232
|
await client.delete(
|
|
1778
2233
|
`/sessions/${encodeURIComponent(sessionId)}`,
|
|
1779
2234
|
tenantId,
|
|
@@ -1782,7 +2237,7 @@ async function deleteSession(client, sessionId, options, signal) {
|
|
|
1782
2237
|
signal
|
|
1783
2238
|
);
|
|
1784
2239
|
}
|
|
1785
|
-
function
|
|
2240
|
+
function resolveTenantId7(client, tenantId) {
|
|
1786
2241
|
const resolved = tenantId ?? client.getDefaultTenantId();
|
|
1787
2242
|
if (!resolved) {
|
|
1788
2243
|
throw new Error(
|
|
@@ -1795,7 +2250,7 @@ function resolveTenantId6(client, tenantId) {
|
|
|
1795
2250
|
// src/routes/vizspec.ts
|
|
1796
2251
|
import crypto5 from "crypto";
|
|
1797
2252
|
async function generateVizSpec(client, input, options, signal) {
|
|
1798
|
-
const tenantId =
|
|
2253
|
+
const tenantId = resolveTenantId8(client, options?.tenantId);
|
|
1799
2254
|
const sessionId = crypto5.randomUUID();
|
|
1800
2255
|
const response = await client.post(
|
|
1801
2256
|
"/vizspec",
|
|
@@ -1817,7 +2272,7 @@ async function generateVizSpec(client, input, options, signal) {
|
|
|
1817
2272
|
);
|
|
1818
2273
|
return response;
|
|
1819
2274
|
}
|
|
1820
|
-
function
|
|
2275
|
+
function resolveTenantId8(client, tenantId) {
|
|
1821
2276
|
const resolved = tenantId ?? client.getDefaultTenantId();
|
|
1822
2277
|
if (!resolved) {
|
|
1823
2278
|
throw new Error(
|
|
@@ -1828,13 +2283,64 @@ function resolveTenantId7(client, tenantId) {
|
|
|
1828
2283
|
}
|
|
1829
2284
|
|
|
1830
2285
|
// src/index.ts
|
|
1831
|
-
var QueryPanelSdkAPI = class {
|
|
2286
|
+
var QueryPanelSdkAPI = class _QueryPanelSdkAPI {
|
|
1832
2287
|
client;
|
|
1833
2288
|
queryEngine;
|
|
1834
|
-
|
|
1835
|
-
|
|
2289
|
+
/**
|
|
2290
|
+
* @param workspaceId - Workspace UUID (same value as your org in the dashboard). Passed to the API as JWT claim `organizationId`.
|
|
2291
|
+
*/
|
|
2292
|
+
constructor(baseUrl, privateKey, workspaceId, options) {
|
|
2293
|
+
if (options?.api) {
|
|
2294
|
+
this.client = options.api;
|
|
2295
|
+
} else {
|
|
2296
|
+
if (!baseUrl) throw new Error("Base URL is required");
|
|
2297
|
+
if (!privateKey) throw new Error("Private key is required");
|
|
2298
|
+
if (!workspaceId) throw new Error("Workspace ID is required");
|
|
2299
|
+
this.client = new ApiClient(baseUrl, privateKey, workspaceId, {
|
|
2300
|
+
defaultTenantId: options?.defaultTenantId,
|
|
2301
|
+
additionalHeaders: options?.additionalHeaders,
|
|
2302
|
+
fetch: options?.fetch
|
|
2303
|
+
});
|
|
2304
|
+
}
|
|
1836
2305
|
this.queryEngine = new QueryEngine();
|
|
1837
2306
|
}
|
|
2307
|
+
/**
|
|
2308
|
+
* Create an SDK instance that uses a request callback instead of HTTP.
|
|
2309
|
+
* Use this when calling the SDK from within your own API so that "API" calls
|
|
2310
|
+
* are handled in-process (e.g. by invoking querypanel-sdk services directly)
|
|
2311
|
+
* and do not cause recursion.
|
|
2312
|
+
*
|
|
2313
|
+
* @param workspaceId - Workspace identifier (sent as `organizationId` in JWTs and API auth)
|
|
2314
|
+
* @param requestHandler - Callback invoked for each logical API request (method, path, body, tenantId, etc.)
|
|
2315
|
+
* @param options - Optional defaultTenantId
|
|
2316
|
+
* @returns QueryPanelSdkAPI instance; attach databases and call ask(), syncSchema(), etc. as usual
|
|
2317
|
+
*
|
|
2318
|
+
* @example
|
|
2319
|
+
* ```ts
|
|
2320
|
+
* const qp = QueryPanelSdkAPI.withCallbacks(
|
|
2321
|
+
* process.env.QUERYPANEL_WORKSPACE_ID!,
|
|
2322
|
+
* async (opts) => {
|
|
2323
|
+
* // Call your in-process services (e.g. querypanel-sdk logic) instead of HTTP
|
|
2324
|
+
* if (opts.path === '/query' || opts.path === '/v2/query') {
|
|
2325
|
+
* const result = await yourSqlGenerator.generate(opts.body, opts.tenantId);
|
|
2326
|
+
* return { data: result, headers: new Headers({ 'x-querypanel-session-id': result.sessionId }) };
|
|
2327
|
+
* }
|
|
2328
|
+
* if (opts.path === '/chart') return { data: await yourChartService.generate(opts.body) };
|
|
2329
|
+
* // ... other paths
|
|
2330
|
+
* return { data: await yourProxy(opts) };
|
|
2331
|
+
* },
|
|
2332
|
+
* { defaultTenantId: process.env.DEFAULT_TENANT_ID }
|
|
2333
|
+
* );
|
|
2334
|
+
* qp.attachPostgres('db', createClient, { tenantFieldName: 'tenant_id' });
|
|
2335
|
+
* const result = await qp.ask('Top 10 orders', { tenantId: 't1', database: 'db' });
|
|
2336
|
+
* ```
|
|
2337
|
+
*/
|
|
2338
|
+
static withCallbacks(workspaceId, requestHandler, options) {
|
|
2339
|
+
const api = new CallbackApiClient(requestHandler, {
|
|
2340
|
+
defaultTenantId: options?.defaultTenantId
|
|
2341
|
+
});
|
|
2342
|
+
return new _QueryPanelSdkAPI("", "", workspaceId, { api });
|
|
2343
|
+
}
|
|
1838
2344
|
// Database attachment methods
|
|
1839
2345
|
attachClickhouse(name, clientFn, options) {
|
|
1840
2346
|
const adapter = new ClickHouseAdapter(clientFn, options);
|
|
@@ -1862,6 +2368,19 @@ var QueryPanelSdkAPI = class {
|
|
|
1862
2368
|
};
|
|
1863
2369
|
this.queryEngine.attachDatabase(name, adapter, metadata);
|
|
1864
2370
|
}
|
|
2371
|
+
attachBigQuery(name, clientFn, options) {
|
|
2372
|
+
const adapter = new BigQueryAdapter(clientFn, options);
|
|
2373
|
+
const metadata = {
|
|
2374
|
+
name,
|
|
2375
|
+
dialect: "bigquery",
|
|
2376
|
+
description: options?.description,
|
|
2377
|
+
tags: options?.tags,
|
|
2378
|
+
tenantFieldName: options?.tenantFieldName,
|
|
2379
|
+
tenantFieldType: options?.tenantFieldType ?? "String",
|
|
2380
|
+
enforceTenantIsolation: options?.tenantFieldName ? options?.enforceTenantIsolation ?? true : void 0
|
|
2381
|
+
};
|
|
2382
|
+
this.queryEngine.attachDatabase(name, adapter, metadata);
|
|
2383
|
+
}
|
|
1865
2384
|
attachDatabase(name, adapter) {
|
|
1866
2385
|
const metadata = {
|
|
1867
2386
|
name,
|
|
@@ -1948,6 +2467,48 @@ var QueryPanelSdkAPI = class {
|
|
|
1948
2467
|
signal
|
|
1949
2468
|
);
|
|
1950
2469
|
}
|
|
2470
|
+
// Embedded dashboard / JWT
|
|
2471
|
+
/**
|
|
2472
|
+
* Creates a JWT for the given tenant (and optional userId, scopes).
|
|
2473
|
+
* Use this when you need to pass a token to the embed (e.g. frontend or demo).
|
|
2474
|
+
* Only available when using the HTTP client (ApiClient); not available when using withCallbacks.
|
|
2475
|
+
*/
|
|
2476
|
+
async createJwt(options) {
|
|
2477
|
+
const tenantId = options.tenantId?.trim();
|
|
2478
|
+
if (!tenantId) {
|
|
2479
|
+
throw new Error("tenantId is required");
|
|
2480
|
+
}
|
|
2481
|
+
if (!(this.client instanceof ApiClient)) {
|
|
2482
|
+
throw new Error(
|
|
2483
|
+
"createJwt is not available when using withCallbacks. Use the SDK with baseUrl + privateKey to create JWTs."
|
|
2484
|
+
);
|
|
2485
|
+
}
|
|
2486
|
+
return await this.client.createJwt(
|
|
2487
|
+
tenantId,
|
|
2488
|
+
options.userId,
|
|
2489
|
+
options.scopes
|
|
2490
|
+
);
|
|
2491
|
+
}
|
|
2492
|
+
/**
|
|
2493
|
+
* Runs SQL against an attached database with tenant isolation applied.
|
|
2494
|
+
* Used by embed run-sql (e.g. dashboard chart execution). Requires a database
|
|
2495
|
+
* to be attached (e.g. via attachPostgres) and options.tenantId.
|
|
2496
|
+
*/
|
|
2497
|
+
async runSqlForDashboard(input, options) {
|
|
2498
|
+
const tenantId = options.tenantId?.trim();
|
|
2499
|
+
if (!tenantId) {
|
|
2500
|
+
throw new Error("tenantId is required");
|
|
2501
|
+
}
|
|
2502
|
+
const databaseName = input.database ?? "db";
|
|
2503
|
+
const params = input.params ?? {};
|
|
2504
|
+
const result = await this.queryEngine.validateAndExecute(
|
|
2505
|
+
input.sql,
|
|
2506
|
+
params,
|
|
2507
|
+
databaseName,
|
|
2508
|
+
tenantId
|
|
2509
|
+
);
|
|
2510
|
+
return { rows: result.rows, fields: result.fields };
|
|
2511
|
+
}
|
|
1951
2512
|
// VizSpec generation
|
|
1952
2513
|
/**
|
|
1953
2514
|
* Generates a VizSpec visualization specification from query results.
|
|
@@ -2391,8 +2952,297 @@ var QueryPanelSdkAPI = class {
|
|
|
2391
2952
|
async deleteActiveChart(id, options, signal) {
|
|
2392
2953
|
await deleteActiveChart(this.client, id, options, signal);
|
|
2393
2954
|
}
|
|
2955
|
+
// Dashboard CRUD operations
|
|
2956
|
+
/**
|
|
2957
|
+
* Creates a new dashboard with BlockNote content.
|
|
2958
|
+
*
|
|
2959
|
+
* @param body - Dashboard configuration including name and BlockNote content
|
|
2960
|
+
* @param options - Tenant, user, and scope options
|
|
2961
|
+
* @param signal - Optional AbortSignal for cancellation
|
|
2962
|
+
* @returns Created dashboard
|
|
2963
|
+
*
|
|
2964
|
+
* @example
|
|
2965
|
+
* ```typescript
|
|
2966
|
+
* const dashboard = await qp.createDashboard({
|
|
2967
|
+
* name: "Sales Dashboard",
|
|
2968
|
+
* description: "Monthly sales metrics",
|
|
2969
|
+
* content_json: blockNoteContent,
|
|
2970
|
+
* editor_type: "blocknote",
|
|
2971
|
+
* }, { tenantId: "tenant_123" });
|
|
2972
|
+
* ```
|
|
2973
|
+
*/
|
|
2974
|
+
async createDashboard(body, options, signal) {
|
|
2975
|
+
return await createDashboard(
|
|
2976
|
+
this.client,
|
|
2977
|
+
body,
|
|
2978
|
+
options,
|
|
2979
|
+
signal
|
|
2980
|
+
);
|
|
2981
|
+
}
|
|
2982
|
+
/**
|
|
2983
|
+
* Lists dashboards with pagination and filtering.
|
|
2984
|
+
*
|
|
2985
|
+
* @param options - Filtering and pagination options
|
|
2986
|
+
* @param signal - Optional AbortSignal for cancellation
|
|
2987
|
+
* @returns Paginated list of dashboards
|
|
2988
|
+
*
|
|
2989
|
+
* @example
|
|
2990
|
+
* ```typescript
|
|
2991
|
+
* const dashboards = await qp.listDashboards({
|
|
2992
|
+
* tenantId: "tenant_123",
|
|
2993
|
+
* status: "deployed",
|
|
2994
|
+
* pagination: { page: 1, limit: 10 },
|
|
2995
|
+
* });
|
|
2996
|
+
* ```
|
|
2997
|
+
*/
|
|
2998
|
+
async listDashboards(options, signal) {
|
|
2999
|
+
return await listDashboards(this.client, options, signal);
|
|
3000
|
+
}
|
|
3001
|
+
/**
|
|
3002
|
+
* Gets a dashboard by ID.
|
|
3003
|
+
*
|
|
3004
|
+
* @param id - Dashboard ID
|
|
3005
|
+
* @param options - Tenant, user, and scope options
|
|
3006
|
+
* @param signal - Optional AbortSignal for cancellation
|
|
3007
|
+
* @returns Dashboard details
|
|
3008
|
+
*
|
|
3009
|
+
* @example
|
|
3010
|
+
* ```typescript
|
|
3011
|
+
* const dashboard = await qp.getDashboard("dash_123", {
|
|
3012
|
+
* tenantId: "tenant_123",
|
|
3013
|
+
* });
|
|
3014
|
+
* ```
|
|
3015
|
+
*/
|
|
3016
|
+
async getDashboard(id, options, signal) {
|
|
3017
|
+
return await getDashboard(
|
|
3018
|
+
this.client,
|
|
3019
|
+
id,
|
|
3020
|
+
options,
|
|
3021
|
+
signal
|
|
3022
|
+
);
|
|
3023
|
+
}
|
|
3024
|
+
/**
|
|
3025
|
+
* Gets a dashboard for a specific tenant.
|
|
3026
|
+
* Returns customer fork if exists, otherwise returns the original dashboard.
|
|
3027
|
+
*
|
|
3028
|
+
* @param id - Dashboard ID
|
|
3029
|
+
* @param tenantId - Tenant ID to check for fork
|
|
3030
|
+
* @param options - Additional auth options
|
|
3031
|
+
* @param signal - Optional AbortSignal for cancellation
|
|
3032
|
+
* @returns Dashboard (fork if exists, original otherwise)
|
|
3033
|
+
*
|
|
3034
|
+
* @example
|
|
3035
|
+
* ```typescript
|
|
3036
|
+
* const dashboard = await qp.getDashboardForTenant(
|
|
3037
|
+
* "dash_123",
|
|
3038
|
+
* "tenant_456",
|
|
3039
|
+
* );
|
|
3040
|
+
* ```
|
|
3041
|
+
*/
|
|
3042
|
+
async getDashboardForTenant(id, tenantId, options, signal) {
|
|
3043
|
+
return await getDashboardForTenant(
|
|
3044
|
+
this.client,
|
|
3045
|
+
id,
|
|
3046
|
+
tenantId,
|
|
3047
|
+
options,
|
|
3048
|
+
signal
|
|
3049
|
+
);
|
|
3050
|
+
}
|
|
3051
|
+
/**
|
|
3052
|
+
* Updates a dashboard.
|
|
3053
|
+
*
|
|
3054
|
+
* @param id - Dashboard ID to update
|
|
3055
|
+
* @param body - Fields to update
|
|
3056
|
+
* @param options - Tenant, user, and scope options
|
|
3057
|
+
* @param signal - Optional AbortSignal for cancellation
|
|
3058
|
+
* @returns Updated dashboard
|
|
3059
|
+
*
|
|
3060
|
+
* @example
|
|
3061
|
+
* ```typescript
|
|
3062
|
+
* const updated = await qp.updateDashboard("dash_123", {
|
|
3063
|
+
* name: "Updated Dashboard",
|
|
3064
|
+
* content_json: newBlockNoteContent,
|
|
3065
|
+
* }, { tenantId: "tenant_123" });
|
|
3066
|
+
* ```
|
|
3067
|
+
*/
|
|
3068
|
+
async updateDashboard(id, body, options, signal) {
|
|
3069
|
+
return await updateDashboard(
|
|
3070
|
+
this.client,
|
|
3071
|
+
id,
|
|
3072
|
+
body,
|
|
3073
|
+
options,
|
|
3074
|
+
signal
|
|
3075
|
+
);
|
|
3076
|
+
}
|
|
3077
|
+
/**
|
|
3078
|
+
* Updates dashboard status (deploy/undeploy).
|
|
3079
|
+
*
|
|
3080
|
+
* @param id - Dashboard ID
|
|
3081
|
+
* @param status - New status
|
|
3082
|
+
* @param options - Tenant, user, and scope options
|
|
3083
|
+
* @param signal - Optional AbortSignal for cancellation
|
|
3084
|
+
* @returns Updated dashboard
|
|
3085
|
+
*
|
|
3086
|
+
* @example
|
|
3087
|
+
* ```typescript
|
|
3088
|
+
* const deployed = await qp.updateDashboardStatus(
|
|
3089
|
+
* "dash_123",
|
|
3090
|
+
* "deployed",
|
|
3091
|
+
* { tenantId: "tenant_123" },
|
|
3092
|
+
* );
|
|
3093
|
+
* ```
|
|
3094
|
+
*/
|
|
3095
|
+
async updateDashboardStatus(id, status, options, signal) {
|
|
3096
|
+
return await updateDashboardStatus(
|
|
3097
|
+
this.client,
|
|
3098
|
+
id,
|
|
3099
|
+
status,
|
|
3100
|
+
options,
|
|
3101
|
+
signal
|
|
3102
|
+
);
|
|
3103
|
+
}
|
|
3104
|
+
/**
|
|
3105
|
+
* Deletes a dashboard.
|
|
3106
|
+
*
|
|
3107
|
+
* @param id - Dashboard ID to delete
|
|
3108
|
+
* @param options - Tenant, user, and scope options
|
|
3109
|
+
* @param signal - Optional AbortSignal for cancellation
|
|
3110
|
+
*
|
|
3111
|
+
* @example
|
|
3112
|
+
* ```typescript
|
|
3113
|
+
* await qp.deleteDashboard("dash_123", { tenantId: "tenant_123" });
|
|
3114
|
+
* ```
|
|
3115
|
+
*/
|
|
3116
|
+
async deleteDashboard(id, options, signal) {
|
|
3117
|
+
await deleteDashboard(this.client, id, options, signal);
|
|
3118
|
+
}
|
|
3119
|
+
// Dashboard Fork operations (Customer Customization)
|
|
3120
|
+
/**
|
|
3121
|
+
* Forks a dashboard for customer customization (copy-on-write).
|
|
3122
|
+
*
|
|
3123
|
+
* Creates a full copy of the dashboard that the customer can edit independently.
|
|
3124
|
+
* The original dashboard remains unchanged.
|
|
3125
|
+
*
|
|
3126
|
+
* @param id - Dashboard ID to fork
|
|
3127
|
+
* @param input - Fork configuration with tenant_id
|
|
3128
|
+
* @param options - Additional auth options
|
|
3129
|
+
* @param signal - Optional AbortSignal for cancellation
|
|
3130
|
+
* @returns Forked dashboard
|
|
3131
|
+
*
|
|
3132
|
+
* @example
|
|
3133
|
+
* ```typescript
|
|
3134
|
+
* const fork = await qp.forkDashboard("dash_123", {
|
|
3135
|
+
* tenant_id: "tenant_456",
|
|
3136
|
+
* name: "Customer's Custom Dashboard",
|
|
3137
|
+
* });
|
|
3138
|
+
* ```
|
|
3139
|
+
*/
|
|
3140
|
+
async forkDashboard(id, input, options, signal) {
|
|
3141
|
+
return await forkDashboard(
|
|
3142
|
+
this.client,
|
|
3143
|
+
id,
|
|
3144
|
+
input,
|
|
3145
|
+
options,
|
|
3146
|
+
signal
|
|
3147
|
+
);
|
|
3148
|
+
}
|
|
3149
|
+
/**
|
|
3150
|
+
* Updates a customer fork.
|
|
3151
|
+
*
|
|
3152
|
+
* @param forkId - Fork ID to update
|
|
3153
|
+
* @param input - Update data including tenant_id
|
|
3154
|
+
* @param options - Additional auth options
|
|
3155
|
+
* @param signal - Optional AbortSignal for cancellation
|
|
3156
|
+
* @returns Updated fork
|
|
3157
|
+
*
|
|
3158
|
+
* @example
|
|
3159
|
+
* ```typescript
|
|
3160
|
+
* const updated = await qp.updateFork("fork_123", {
|
|
3161
|
+
* tenant_id: "tenant_456",
|
|
3162
|
+
* content_json: newBlockNoteContent,
|
|
3163
|
+
* });
|
|
3164
|
+
* ```
|
|
3165
|
+
*/
|
|
3166
|
+
async updateFork(forkId, input, options, signal) {
|
|
3167
|
+
return await updateFork(
|
|
3168
|
+
this.client,
|
|
3169
|
+
forkId,
|
|
3170
|
+
input,
|
|
3171
|
+
options,
|
|
3172
|
+
signal
|
|
3173
|
+
);
|
|
3174
|
+
}
|
|
3175
|
+
/**
|
|
3176
|
+
* Rollbacks a fork to the original dashboard.
|
|
3177
|
+
* This deletes the fork and the customer will see the original again.
|
|
3178
|
+
*
|
|
3179
|
+
* @param forkId - Fork ID to rollback
|
|
3180
|
+
* @param tenantId - Tenant ID
|
|
3181
|
+
* @param options - Additional auth options
|
|
3182
|
+
* @param signal - Optional AbortSignal for cancellation
|
|
3183
|
+
* @returns Original dashboard
|
|
3184
|
+
*
|
|
3185
|
+
* @example
|
|
3186
|
+
* ```typescript
|
|
3187
|
+
* const original = await qp.rollbackFork("fork_123", "tenant_456");
|
|
3188
|
+
* ```
|
|
3189
|
+
*/
|
|
3190
|
+
async rollbackFork(forkId, tenantId, options, signal) {
|
|
3191
|
+
return await rollbackFork(
|
|
3192
|
+
this.client,
|
|
3193
|
+
forkId,
|
|
3194
|
+
tenantId,
|
|
3195
|
+
options,
|
|
3196
|
+
signal
|
|
3197
|
+
);
|
|
3198
|
+
}
|
|
3199
|
+
/**
|
|
3200
|
+
* Deletes a customer fork.
|
|
3201
|
+
*
|
|
3202
|
+
* @param forkId - Fork ID to delete
|
|
3203
|
+
* @param tenantId - Tenant ID
|
|
3204
|
+
* @param options - Additional auth options
|
|
3205
|
+
* @param signal - Optional AbortSignal for cancellation
|
|
3206
|
+
*
|
|
3207
|
+
* @example
|
|
3208
|
+
* ```typescript
|
|
3209
|
+
* await qp.deleteFork("fork_123", "tenant_456");
|
|
3210
|
+
* ```
|
|
3211
|
+
*/
|
|
3212
|
+
async deleteFork(forkId, tenantId, options, signal) {
|
|
3213
|
+
await deleteFork(
|
|
3214
|
+
this.client,
|
|
3215
|
+
forkId,
|
|
3216
|
+
tenantId,
|
|
3217
|
+
options,
|
|
3218
|
+
signal
|
|
3219
|
+
);
|
|
3220
|
+
}
|
|
3221
|
+
/**
|
|
3222
|
+
* Lists all customer forks for a tenant.
|
|
3223
|
+
*
|
|
3224
|
+
* @param tenantId - Tenant ID
|
|
3225
|
+
* @param options - Additional auth options
|
|
3226
|
+
* @param signal - Optional AbortSignal for cancellation
|
|
3227
|
+
* @returns Array of forks
|
|
3228
|
+
*
|
|
3229
|
+
* @example
|
|
3230
|
+
* ```typescript
|
|
3231
|
+
* const forks = await qp.listForksForTenant("tenant_456");
|
|
3232
|
+
* ```
|
|
3233
|
+
*/
|
|
3234
|
+
async listForksForTenant(tenantId, options, signal) {
|
|
3235
|
+
return await listForksForTenant(
|
|
3236
|
+
this.client,
|
|
3237
|
+
tenantId,
|
|
3238
|
+
options,
|
|
3239
|
+
signal
|
|
3240
|
+
);
|
|
3241
|
+
}
|
|
2394
3242
|
};
|
|
2395
3243
|
export {
|
|
3244
|
+
BigQueryAdapter,
|
|
3245
|
+
CallbackApiClient,
|
|
2396
3246
|
ClickHouseAdapter,
|
|
2397
3247
|
PostgresAdapter,
|
|
2398
3248
|
QueryErrorCode,
|