@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.cjs
CHANGED
|
@@ -30,6 +30,8 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
30
30
|
// src/index.ts
|
|
31
31
|
var index_exports = {};
|
|
32
32
|
__export(index_exports, {
|
|
33
|
+
BigQueryAdapter: () => BigQueryAdapter,
|
|
34
|
+
CallbackApiClient: () => CallbackApiClient,
|
|
33
35
|
ClickHouseAdapter: () => ClickHouseAdapter,
|
|
34
36
|
PostgresAdapter: () => PostgresAdapter,
|
|
35
37
|
QueryErrorCode: () => QueryErrorCode,
|
|
@@ -39,6 +41,186 @@ __export(index_exports, {
|
|
|
39
41
|
});
|
|
40
42
|
module.exports = __toCommonJS(index_exports);
|
|
41
43
|
|
|
44
|
+
// src/adapters/bigquery.ts
|
|
45
|
+
var BigQueryAdapter = class {
|
|
46
|
+
constructor(clientFn, options) {
|
|
47
|
+
this.clientFn = clientFn;
|
|
48
|
+
this.projectId = options.projectId;
|
|
49
|
+
this.datasetProjectId = options.datasetProjectId ?? options.projectId;
|
|
50
|
+
this.defaultDataset = options.dataset;
|
|
51
|
+
this.databaseName = options.database ?? options.dataset;
|
|
52
|
+
this.kind = options.kind ?? "bigquery";
|
|
53
|
+
if (options.allowedTables) {
|
|
54
|
+
this.allowedTables = normalizeTableFilter(
|
|
55
|
+
options.allowedTables,
|
|
56
|
+
this.defaultDataset
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
projectId;
|
|
61
|
+
datasetProjectId;
|
|
62
|
+
defaultDataset;
|
|
63
|
+
databaseName;
|
|
64
|
+
kind;
|
|
65
|
+
allowedTables;
|
|
66
|
+
async execute(sql, params) {
|
|
67
|
+
if (this.allowedTables) {
|
|
68
|
+
this.validateQueryTables(sql);
|
|
69
|
+
}
|
|
70
|
+
const result = await this.clientFn({
|
|
71
|
+
query: sql,
|
|
72
|
+
params,
|
|
73
|
+
dryRun: false
|
|
74
|
+
});
|
|
75
|
+
return { fields: result.fields, rows: result.rows };
|
|
76
|
+
}
|
|
77
|
+
async validate(sql, params) {
|
|
78
|
+
await this.clientFn({
|
|
79
|
+
query: sql,
|
|
80
|
+
params,
|
|
81
|
+
dryRun: true
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
getDialect() {
|
|
85
|
+
return "bigquery";
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Introspect using BigQuery INFORMATION_SCHEMA (TABLES and COLUMNS).
|
|
89
|
+
* Restricts to the default dataset (or allowedTables) and returns tables/columns only.
|
|
90
|
+
*/
|
|
91
|
+
async introspect(options) {
|
|
92
|
+
const tablesToIntrospect = options?.tables ? normalizeTableFilter(options.tables, this.defaultDataset) : this.allowedTables;
|
|
93
|
+
const normalizedTables = tablesToIntrospect ?? [];
|
|
94
|
+
const tablesSql = buildTablesQuery(
|
|
95
|
+
this.datasetProjectId,
|
|
96
|
+
this.defaultDataset,
|
|
97
|
+
normalizedTables
|
|
98
|
+
);
|
|
99
|
+
const tablesResult = await this.clientFn({
|
|
100
|
+
query: tablesSql,
|
|
101
|
+
dryRun: false
|
|
102
|
+
});
|
|
103
|
+
const tableRows = tablesResult.rows;
|
|
104
|
+
const columnsSql = buildColumnsQuery(
|
|
105
|
+
this.datasetProjectId,
|
|
106
|
+
this.defaultDataset,
|
|
107
|
+
normalizedTables
|
|
108
|
+
);
|
|
109
|
+
const columnsResult = await this.clientFn({
|
|
110
|
+
query: columnsSql,
|
|
111
|
+
dryRun: false
|
|
112
|
+
});
|
|
113
|
+
const columnRows = columnsResult.rows;
|
|
114
|
+
const tablesByKey = /* @__PURE__ */ new Map();
|
|
115
|
+
for (const row of tableRows) {
|
|
116
|
+
const key = tableKey(row.table_schema, row.table_name);
|
|
117
|
+
const table = {
|
|
118
|
+
name: row.table_name,
|
|
119
|
+
schema: row.table_schema,
|
|
120
|
+
type: asTableType(row.table_type),
|
|
121
|
+
columns: []
|
|
122
|
+
};
|
|
123
|
+
tablesByKey.set(key, table);
|
|
124
|
+
}
|
|
125
|
+
for (const row of columnRows) {
|
|
126
|
+
const key = tableKey(row.table_schema, row.table_name);
|
|
127
|
+
const table = tablesByKey.get(key);
|
|
128
|
+
if (!table) continue;
|
|
129
|
+
const column = {
|
|
130
|
+
name: row.column_name,
|
|
131
|
+
type: row.data_type,
|
|
132
|
+
isPrimaryKey: false
|
|
133
|
+
};
|
|
134
|
+
table.columns.push(column);
|
|
135
|
+
}
|
|
136
|
+
const tables = Array.from(tablesByKey.values()).sort((a, b) => {
|
|
137
|
+
if (a.schema === b.schema) {
|
|
138
|
+
return a.name.localeCompare(b.name);
|
|
139
|
+
}
|
|
140
|
+
return a.schema.localeCompare(b.schema);
|
|
141
|
+
});
|
|
142
|
+
return {
|
|
143
|
+
db: {
|
|
144
|
+
kind: this.kind,
|
|
145
|
+
name: this.databaseName
|
|
146
|
+
},
|
|
147
|
+
tables,
|
|
148
|
+
introspectedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
validateQueryTables(sql) {
|
|
152
|
+
if (!this.allowedTables || this.allowedTables.length === 0) {
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
const allowedSet = new Set(
|
|
156
|
+
this.allowedTables.map((t) => tableKey(t.dataset, t.table))
|
|
157
|
+
);
|
|
158
|
+
const tablePattern = /(?:FROM|JOIN)\s+`?(?:[\w-]+\.)?([\w-]+)\.([\w-]+)`?|(?:FROM|JOIN)\s+`?([\w-]+)`?/gi;
|
|
159
|
+
const matches = sql.matchAll(tablePattern);
|
|
160
|
+
for (const match of matches) {
|
|
161
|
+
const dataset = match[1] ?? match[3] ?? this.defaultDataset;
|
|
162
|
+
const table = match[2] ?? match[3];
|
|
163
|
+
if (table) {
|
|
164
|
+
const key = tableKey(dataset, table);
|
|
165
|
+
if (!allowedSet.has(key)) {
|
|
166
|
+
throw new Error(
|
|
167
|
+
`Query references table "${dataset}.${table}" which is not in the allowed tables list`
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
};
|
|
174
|
+
function normalizeTableFilter(tables, defaultDataset) {
|
|
175
|
+
if (!tables?.length) return [];
|
|
176
|
+
const normalized = [];
|
|
177
|
+
const seen = /* @__PURE__ */ new Set();
|
|
178
|
+
for (const raw of tables) {
|
|
179
|
+
if (!raw?.trim()) continue;
|
|
180
|
+
const trimmed = raw.trim();
|
|
181
|
+
const parts = trimmed.split(".");
|
|
182
|
+
const table = parts.pop() ?? "";
|
|
183
|
+
const dataset = parts.pop() ?? defaultDataset;
|
|
184
|
+
const key = tableKey(dataset, table);
|
|
185
|
+
if (seen.has(key)) continue;
|
|
186
|
+
seen.add(key);
|
|
187
|
+
normalized.push({ dataset, table });
|
|
188
|
+
}
|
|
189
|
+
return normalized;
|
|
190
|
+
}
|
|
191
|
+
function tableKey(dataset, table) {
|
|
192
|
+
return `${dataset}.${table}`;
|
|
193
|
+
}
|
|
194
|
+
function buildTablesQuery(datasetProjectId, dataset, tables) {
|
|
195
|
+
const qualifier = `\`${datasetProjectId}.${dataset}.INFORMATION_SCHEMA.TABLES\``;
|
|
196
|
+
const filter = tables.length > 0 ? ` AND table_name IN (${tables.map((t) => `'${escapeSql(t.table)}'`).join(", ")})` : "";
|
|
197
|
+
return `SELECT table_name, table_schema, table_type
|
|
198
|
+
FROM ${qualifier}
|
|
199
|
+
WHERE table_schema = '${escapeSql(dataset)}'${filter}
|
|
200
|
+
ORDER BY table_schema, table_name`;
|
|
201
|
+
}
|
|
202
|
+
function buildColumnsQuery(datasetProjectId, dataset, tables) {
|
|
203
|
+
const qualifier = `\`${datasetProjectId}.${dataset}.INFORMATION_SCHEMA.COLUMNS\``;
|
|
204
|
+
const filter = tables.length > 0 ? ` AND table_name IN (${tables.map((t) => `'${escapeSql(t.table)}'`).join(", ")})` : "";
|
|
205
|
+
return `SELECT table_name, table_schema, column_name, data_type, ordinal_position, is_nullable
|
|
206
|
+
FROM ${qualifier}
|
|
207
|
+
WHERE table_schema = '${escapeSql(dataset)}'${filter}
|
|
208
|
+
ORDER BY table_schema, table_name, ordinal_position`;
|
|
209
|
+
}
|
|
210
|
+
function escapeSql(value) {
|
|
211
|
+
return value.replace(/\\/g, "\\\\").replace(/'/g, "''");
|
|
212
|
+
}
|
|
213
|
+
function asTableType(tableType) {
|
|
214
|
+
const normalized = tableType?.toLowerCase() ?? "";
|
|
215
|
+
if (normalized.includes("view")) {
|
|
216
|
+
return normalized.includes("materialized") ? "materialized_view" : "view";
|
|
217
|
+
}
|
|
218
|
+
if (normalized.includes("external") || normalized.includes("snapshot") || normalized.includes("clone")) {
|
|
219
|
+
return "table";
|
|
220
|
+
}
|
|
221
|
+
return "table";
|
|
222
|
+
}
|
|
223
|
+
|
|
42
224
|
// src/utils/clickhouse.ts
|
|
43
225
|
var WRAPPER_REGEX = /^(Nullable|LowCardinality|SimpleAggregateFunction)\((.+)\)$/i;
|
|
44
226
|
function unwrapTypeModifiers(type) {
|
|
@@ -108,7 +290,7 @@ var ClickHouseAdapter = class {
|
|
|
108
290
|
this.defaultFormat = options.defaultFormat ?? "JSONEachRow";
|
|
109
291
|
this.kind = options.kind ?? "clickhouse";
|
|
110
292
|
if (options.allowedTables) {
|
|
111
|
-
this.allowedTables =
|
|
293
|
+
this.allowedTables = normalizeTableFilter2(options.allowedTables);
|
|
112
294
|
}
|
|
113
295
|
}
|
|
114
296
|
databaseName;
|
|
@@ -146,7 +328,7 @@ var ClickHouseAdapter = class {
|
|
|
146
328
|
* No indexes, constraints, or statistics
|
|
147
329
|
*/
|
|
148
330
|
async introspect(options) {
|
|
149
|
-
const tablesToIntrospect = options?.tables ?
|
|
331
|
+
const tablesToIntrospect = options?.tables ? normalizeTableFilter2(options.tables) : this.allowedTables;
|
|
150
332
|
const allowTables = tablesToIntrospect ?? [];
|
|
151
333
|
const hasFilter = allowTables.length > 0;
|
|
152
334
|
const queryParams = {
|
|
@@ -186,7 +368,7 @@ var ClickHouseAdapter = class {
|
|
|
186
368
|
const base = {
|
|
187
369
|
name: table.name,
|
|
188
370
|
schema: this.databaseName,
|
|
189
|
-
type:
|
|
371
|
+
type: asTableType2(table.engine),
|
|
190
372
|
columns: tableColumns
|
|
191
373
|
};
|
|
192
374
|
const comment = sanitize(table.comment);
|
|
@@ -264,7 +446,7 @@ function normalizePayload(payload) {
|
|
|
264
446
|
}
|
|
265
447
|
return [];
|
|
266
448
|
}
|
|
267
|
-
function
|
|
449
|
+
function normalizeTableFilter2(tables) {
|
|
268
450
|
if (!tables?.length) return [];
|
|
269
451
|
const seen = /* @__PURE__ */ new Set();
|
|
270
452
|
const normalized = [];
|
|
@@ -292,7 +474,7 @@ function transformColumnRow(row) {
|
|
|
292
474
|
if (comment !== void 0) column.comment = comment;
|
|
293
475
|
return column;
|
|
294
476
|
}
|
|
295
|
-
function
|
|
477
|
+
function asTableType2(engine) {
|
|
296
478
|
if (typeof engine === "string") {
|
|
297
479
|
const normalized = engine.toLowerCase();
|
|
298
480
|
if (normalized.includes("view")) {
|
|
@@ -321,7 +503,7 @@ var PostgresAdapter = class {
|
|
|
321
503
|
this.defaultSchema = options.defaultSchema ?? "public";
|
|
322
504
|
this.kind = options.kind ?? "postgres";
|
|
323
505
|
if (options.allowedTables) {
|
|
324
|
-
this.allowedTables =
|
|
506
|
+
this.allowedTables = normalizeTableFilter3(
|
|
325
507
|
options.allowedTables,
|
|
326
508
|
this.defaultSchema
|
|
327
509
|
);
|
|
@@ -348,7 +530,7 @@ var PostgresAdapter = class {
|
|
|
348
530
|
return;
|
|
349
531
|
}
|
|
350
532
|
const allowedSet = new Set(
|
|
351
|
-
this.allowedTables.map((t) =>
|
|
533
|
+
this.allowedTables.map((t) => tableKey2(t.schema, t.table))
|
|
352
534
|
);
|
|
353
535
|
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*/)");
|
|
354
536
|
const tablePattern = /(?:FROM|JOIN)\s+(?:ONLY\s+)?(?:([a-zA-Z_][a-zA-Z0-9_]*)\.)?(["']?[a-zA-Z_][a-zA-Z0-9_]*["']?)/gi;
|
|
@@ -357,7 +539,7 @@ var PostgresAdapter = class {
|
|
|
357
539
|
const schema = match[1] ?? this.defaultSchema;
|
|
358
540
|
const table = match[2]?.replace(/['"]/g, "");
|
|
359
541
|
if (table) {
|
|
360
|
-
const key =
|
|
542
|
+
const key = tableKey2(schema, table);
|
|
361
543
|
if (!allowedSet.has(key)) {
|
|
362
544
|
throw new Error(
|
|
363
545
|
`Query references table "${schema}.${table}" which is not in the allowed tables list`
|
|
@@ -367,29 +549,31 @@ var PostgresAdapter = class {
|
|
|
367
549
|
}
|
|
368
550
|
}
|
|
369
551
|
/**
|
|
370
|
-
* Convert
|
|
371
|
-
* PostgreSQL expects $1, $2, $3 in SQL and an array of values [val1, val2, val3]
|
|
552
|
+
* Convert params to positional array for PostgreSQL.
|
|
553
|
+
* PostgreSQL expects $1, $2, $3 in SQL and an array of values [val1, val2, val3].
|
|
554
|
+
* When the record has numeric keys ("1", "2", "3") — e.g. from mapGeneratedParams —
|
|
555
|
+
* use only those in order so placeholder order matches SQL. Otherwise fall back to
|
|
556
|
+
* named keys in alphabetical order for backward compatibility.
|
|
372
557
|
*/
|
|
373
558
|
convertNamedToPositionalParams(params) {
|
|
374
559
|
const numericKeys = Object.keys(params).filter((k) => /^\d+$/.test(k)).map((k) => Number.parseInt(k, 10)).sort((a, b) => a - b);
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
560
|
+
if (numericKeys.length > 0) {
|
|
561
|
+
const positionalParams = [];
|
|
562
|
+
for (const key of numericKeys) {
|
|
563
|
+
let val = params[String(key)];
|
|
564
|
+
if (typeof val === "string") {
|
|
565
|
+
const match = val.match(/^<([a-zA-Z0-9_]+)>$/);
|
|
566
|
+
const namedKey = match?.[1];
|
|
567
|
+
if (namedKey && namedKey in params) {
|
|
568
|
+
val = params[namedKey];
|
|
569
|
+
}
|
|
384
570
|
}
|
|
571
|
+
positionalParams.push(val);
|
|
385
572
|
}
|
|
386
|
-
positionalParams
|
|
387
|
-
}
|
|
388
|
-
for (const key of namedKeys) {
|
|
389
|
-
const val = params[key];
|
|
390
|
-
positionalParams.push(val);
|
|
573
|
+
return positionalParams;
|
|
391
574
|
}
|
|
392
|
-
|
|
575
|
+
const namedKeys = Object.keys(params).filter((k) => !/^\d+$/.test(k)).sort();
|
|
576
|
+
return namedKeys.map((key) => params[key]);
|
|
393
577
|
}
|
|
394
578
|
async validate(sql, params) {
|
|
395
579
|
let paramArray;
|
|
@@ -406,23 +590,23 @@ var PostgresAdapter = class {
|
|
|
406
590
|
* No indexes, constraints, or statistics
|
|
407
591
|
*/
|
|
408
592
|
async introspect(options) {
|
|
409
|
-
const tablesToIntrospect = options?.tables ?
|
|
593
|
+
const tablesToIntrospect = options?.tables ? normalizeTableFilter3(options.tables, this.defaultSchema) : this.allowedTables;
|
|
410
594
|
const normalizedTables = tablesToIntrospect ?? [];
|
|
411
595
|
const tablesResult = await this.clientFn(
|
|
412
|
-
|
|
596
|
+
buildTablesQuery2(normalizedTables)
|
|
413
597
|
);
|
|
414
598
|
const tableRows = tablesResult.rows;
|
|
415
599
|
const columnsResult = await this.clientFn(
|
|
416
|
-
|
|
600
|
+
buildColumnsQuery2(normalizedTables)
|
|
417
601
|
);
|
|
418
602
|
const columnRows = columnsResult.rows;
|
|
419
603
|
const tablesByKey = /* @__PURE__ */ new Map();
|
|
420
604
|
for (const row of tableRows) {
|
|
421
|
-
const key =
|
|
605
|
+
const key = tableKey2(row.schema_name, row.table_name);
|
|
422
606
|
const table = {
|
|
423
607
|
name: row.table_name,
|
|
424
608
|
schema: row.schema_name,
|
|
425
|
-
type:
|
|
609
|
+
type: asTableType3(row.table_type),
|
|
426
610
|
columns: []
|
|
427
611
|
};
|
|
428
612
|
const comment = sanitize2(row.comment);
|
|
@@ -432,7 +616,7 @@ var PostgresAdapter = class {
|
|
|
432
616
|
tablesByKey.set(key, table);
|
|
433
617
|
}
|
|
434
618
|
for (const row of columnRows) {
|
|
435
|
-
const key =
|
|
619
|
+
const key = tableKey2(row.table_schema, row.table_name);
|
|
436
620
|
const table = tablesByKey.get(key);
|
|
437
621
|
if (!table) continue;
|
|
438
622
|
const column = {
|
|
@@ -462,7 +646,7 @@ var PostgresAdapter = class {
|
|
|
462
646
|
};
|
|
463
647
|
}
|
|
464
648
|
};
|
|
465
|
-
function
|
|
649
|
+
function normalizeTableFilter3(tables, defaultSchema) {
|
|
466
650
|
if (!tables?.length) return [];
|
|
467
651
|
const normalized = [];
|
|
468
652
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -476,14 +660,14 @@ function normalizeTableFilter2(tables, defaultSchema) {
|
|
|
476
660
|
if (!isSafeIdentifier(schema) || !isSafeIdentifier(table)) {
|
|
477
661
|
continue;
|
|
478
662
|
}
|
|
479
|
-
const key =
|
|
663
|
+
const key = tableKey2(schema, table);
|
|
480
664
|
if (seen.has(key)) continue;
|
|
481
665
|
seen.add(key);
|
|
482
666
|
normalized.push({ schema, table });
|
|
483
667
|
}
|
|
484
668
|
return normalized;
|
|
485
669
|
}
|
|
486
|
-
function
|
|
670
|
+
function buildTablesQuery2(tables) {
|
|
487
671
|
const filter = buildFilterClause(tables, "n.nspname", "c.relname");
|
|
488
672
|
return `SELECT
|
|
489
673
|
c.relname AS table_name,
|
|
@@ -502,7 +686,7 @@ function buildTablesQuery(tables) {
|
|
|
502
686
|
${filter}
|
|
503
687
|
ORDER BY n.nspname, c.relname;`;
|
|
504
688
|
}
|
|
505
|
-
function
|
|
689
|
+
function buildColumnsQuery2(tables) {
|
|
506
690
|
const filter = buildFilterClause(
|
|
507
691
|
tables,
|
|
508
692
|
"cols.table_schema",
|
|
@@ -547,13 +731,13 @@ function buildFilterClause(tables, schemaExpr, tableExpr) {
|
|
|
547
731
|
});
|
|
548
732
|
return `AND (${clauses.join(" OR ")})`;
|
|
549
733
|
}
|
|
550
|
-
function
|
|
734
|
+
function tableKey2(schema, table) {
|
|
551
735
|
return `${schema}.${table}`;
|
|
552
736
|
}
|
|
553
737
|
function isSafeIdentifier(value) {
|
|
554
738
|
return /^[A-Za-z_][A-Za-z0-9_]*$/.test(value);
|
|
555
739
|
}
|
|
556
|
-
function
|
|
740
|
+
function asTableType3(value) {
|
|
557
741
|
const normalized = value.toLowerCase();
|
|
558
742
|
if (normalized.includes("view")) {
|
|
559
743
|
return normalized.includes("materialized") ? "materialized_view" : "view";
|
|
@@ -601,6 +785,13 @@ var ApiClient = class {
|
|
|
601
785
|
getDefaultTenantId() {
|
|
602
786
|
return this.defaultTenantId;
|
|
603
787
|
}
|
|
788
|
+
/**
|
|
789
|
+
* Create a JWT for the given tenant (and optional userId, scopes).
|
|
790
|
+
* Use this when you need to pass a token to the embed (e.g. frontend or demo).
|
|
791
|
+
*/
|
|
792
|
+
async createJwt(tenantId, userId, scopes) {
|
|
793
|
+
return await this.generateJWT(tenantId, userId, scopes);
|
|
794
|
+
}
|
|
604
795
|
async get(path, tenantId, userId, scopes, signal, sessionId) {
|
|
605
796
|
return await this.request(path, {
|
|
606
797
|
method: "GET",
|
|
@@ -813,6 +1004,111 @@ var ApiClient = class {
|
|
|
813
1004
|
}
|
|
814
1005
|
};
|
|
815
1006
|
|
|
1007
|
+
// src/core/callback-client.ts
|
|
1008
|
+
var CallbackApiClient = class {
|
|
1009
|
+
requestHandler;
|
|
1010
|
+
defaultTenantId;
|
|
1011
|
+
constructor(requestHandler, options) {
|
|
1012
|
+
this.requestHandler = requestHandler;
|
|
1013
|
+
this.defaultTenantId = options?.defaultTenantId;
|
|
1014
|
+
}
|
|
1015
|
+
getDefaultTenantId() {
|
|
1016
|
+
return this.defaultTenantId;
|
|
1017
|
+
}
|
|
1018
|
+
async request(opts) {
|
|
1019
|
+
const { data, headers } = await this.requestHandler({
|
|
1020
|
+
method: opts.method,
|
|
1021
|
+
path: opts.path,
|
|
1022
|
+
body: opts.body,
|
|
1023
|
+
tenantId: opts.tenantId,
|
|
1024
|
+
userId: opts.userId,
|
|
1025
|
+
scopes: opts.scopes,
|
|
1026
|
+
signal: opts.signal,
|
|
1027
|
+
sessionId: opts.sessionId
|
|
1028
|
+
});
|
|
1029
|
+
return { data, headers };
|
|
1030
|
+
}
|
|
1031
|
+
async get(path, tenantId, userId, scopes, signal, sessionId) {
|
|
1032
|
+
const { data } = await this.request({
|
|
1033
|
+
method: "GET",
|
|
1034
|
+
path,
|
|
1035
|
+
tenantId,
|
|
1036
|
+
userId,
|
|
1037
|
+
scopes,
|
|
1038
|
+
signal,
|
|
1039
|
+
sessionId
|
|
1040
|
+
});
|
|
1041
|
+
return data;
|
|
1042
|
+
}
|
|
1043
|
+
async post(path, body, tenantId, userId, scopes, signal, sessionId) {
|
|
1044
|
+
const { data } = await this.request({
|
|
1045
|
+
method: "POST",
|
|
1046
|
+
path,
|
|
1047
|
+
body,
|
|
1048
|
+
tenantId,
|
|
1049
|
+
userId,
|
|
1050
|
+
scopes,
|
|
1051
|
+
signal,
|
|
1052
|
+
sessionId
|
|
1053
|
+
});
|
|
1054
|
+
return data;
|
|
1055
|
+
}
|
|
1056
|
+
async postWithHeaders(path, body, tenantId, userId, scopes, signal, sessionId) {
|
|
1057
|
+
const { data, headers } = await this.request({
|
|
1058
|
+
method: "POST",
|
|
1059
|
+
path,
|
|
1060
|
+
body,
|
|
1061
|
+
tenantId,
|
|
1062
|
+
userId,
|
|
1063
|
+
scopes,
|
|
1064
|
+
signal,
|
|
1065
|
+
sessionId
|
|
1066
|
+
});
|
|
1067
|
+
return {
|
|
1068
|
+
data,
|
|
1069
|
+
headers: headers ?? new Headers()
|
|
1070
|
+
};
|
|
1071
|
+
}
|
|
1072
|
+
async put(path, body, tenantId, userId, scopes, signal, sessionId) {
|
|
1073
|
+
const { data } = await this.request({
|
|
1074
|
+
method: "PUT",
|
|
1075
|
+
path,
|
|
1076
|
+
body,
|
|
1077
|
+
tenantId,
|
|
1078
|
+
userId,
|
|
1079
|
+
scopes,
|
|
1080
|
+
signal,
|
|
1081
|
+
sessionId
|
|
1082
|
+
});
|
|
1083
|
+
return data;
|
|
1084
|
+
}
|
|
1085
|
+
async patch(path, body, tenantId, userId, scopes, signal, sessionId) {
|
|
1086
|
+
const { data } = await this.request({
|
|
1087
|
+
method: "PATCH",
|
|
1088
|
+
path,
|
|
1089
|
+
body,
|
|
1090
|
+
tenantId,
|
|
1091
|
+
userId,
|
|
1092
|
+
scopes,
|
|
1093
|
+
signal,
|
|
1094
|
+
sessionId
|
|
1095
|
+
});
|
|
1096
|
+
return data;
|
|
1097
|
+
}
|
|
1098
|
+
async delete(path, tenantId, userId, scopes, signal, sessionId) {
|
|
1099
|
+
const { data } = await this.request({
|
|
1100
|
+
method: "DELETE",
|
|
1101
|
+
path,
|
|
1102
|
+
tenantId,
|
|
1103
|
+
userId,
|
|
1104
|
+
scopes,
|
|
1105
|
+
signal,
|
|
1106
|
+
sessionId
|
|
1107
|
+
});
|
|
1108
|
+
return data;
|
|
1109
|
+
}
|
|
1110
|
+
};
|
|
1111
|
+
|
|
816
1112
|
// src/core/query-engine.ts
|
|
817
1113
|
var QueryEngine = class {
|
|
818
1114
|
databases = /* @__PURE__ */ new Map();
|
|
@@ -875,6 +1171,12 @@ var QueryEngine = class {
|
|
|
875
1171
|
return [];
|
|
876
1172
|
}
|
|
877
1173
|
}
|
|
1174
|
+
/**
|
|
1175
|
+
* Maps API params (array in SQL placeholder order $1, $2, $3) to ParamRecord.
|
|
1176
|
+
* Sets both numeric keys ("1", "2", "3") and named keys so that:
|
|
1177
|
+
* - Postgres adapter uses numeric order for positional params (matches SQL).
|
|
1178
|
+
* - Named keys remain for tenant isolation / other lookups.
|
|
1179
|
+
*/
|
|
878
1180
|
mapGeneratedParams(params) {
|
|
879
1181
|
const record = {};
|
|
880
1182
|
params.forEach((param, index) => {
|
|
@@ -882,6 +1184,7 @@ var QueryEngine = class {
|
|
|
882
1184
|
if (value === void 0) {
|
|
883
1185
|
return;
|
|
884
1186
|
}
|
|
1187
|
+
record[String(index + 1)] = value;
|
|
885
1188
|
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);
|
|
886
1189
|
const key = nameCandidate.replace(/[{}:$]/g, "").trim();
|
|
887
1190
|
record[key] = value;
|
|
@@ -902,6 +1205,9 @@ var QueryEngine = class {
|
|
|
902
1205
|
const paramKey = tenantField;
|
|
903
1206
|
params[paramKey] = tenantId;
|
|
904
1207
|
tenantPredicate = `${tenantField} = {${tenantField}:${metadata.tenantFieldType ?? "String"}}`;
|
|
1208
|
+
} else if (metadata.dialect === "bigquery") {
|
|
1209
|
+
params[tenantField] = tenantId;
|
|
1210
|
+
tenantPredicate = `${tenantField} = @${tenantField}`;
|
|
905
1211
|
} else {
|
|
906
1212
|
const escapedId = tenantId.replace(/'/g, "''");
|
|
907
1213
|
tenantPredicate = `${tenantField} = '${escapedId}'`;
|
|
@@ -1200,20 +1506,171 @@ async function deleteActiveChart(client, id, options, signal) {
|
|
|
1200
1506
|
signal
|
|
1201
1507
|
);
|
|
1202
1508
|
}
|
|
1203
|
-
function resolveTenantId2(client, tenantId) {
|
|
1204
|
-
const resolved = tenantId ?? client.getDefaultTenantId();
|
|
1205
|
-
if (!resolved) {
|
|
1206
|
-
throw new Error(
|
|
1207
|
-
"tenantId is required. Provide it per request or via defaultTenantId option."
|
|
1208
|
-
);
|
|
1209
|
-
}
|
|
1210
|
-
return resolved;
|
|
1211
|
-
}
|
|
1509
|
+
function resolveTenantId2(client, tenantId) {
|
|
1510
|
+
const resolved = tenantId ?? client.getDefaultTenantId();
|
|
1511
|
+
if (!resolved) {
|
|
1512
|
+
throw new Error(
|
|
1513
|
+
"tenantId is required. Provide it per request or via defaultTenantId option."
|
|
1514
|
+
);
|
|
1515
|
+
}
|
|
1516
|
+
return resolved;
|
|
1517
|
+
}
|
|
1518
|
+
|
|
1519
|
+
// src/routes/dashboards.ts
|
|
1520
|
+
function resolveTenantId3(client, tenantId) {
|
|
1521
|
+
const resolved = tenantId ?? client.getDefaultTenantId();
|
|
1522
|
+
if (resolved === void 0) {
|
|
1523
|
+
throw new Error("tenantId is required (option or client default)");
|
|
1524
|
+
}
|
|
1525
|
+
return resolved;
|
|
1526
|
+
}
|
|
1527
|
+
async function createDashboard(client, body, options, signal) {
|
|
1528
|
+
const tenantId = resolveTenantId3(client, options?.tenantId);
|
|
1529
|
+
return await client.post(
|
|
1530
|
+
"/dashboards",
|
|
1531
|
+
body,
|
|
1532
|
+
tenantId,
|
|
1533
|
+
options?.userId,
|
|
1534
|
+
options?.scopes,
|
|
1535
|
+
signal
|
|
1536
|
+
);
|
|
1537
|
+
}
|
|
1538
|
+
async function listDashboards(client, options, signal) {
|
|
1539
|
+
const tenantId = resolveTenantId3(client, options?.tenantId);
|
|
1540
|
+
const params = new URLSearchParams();
|
|
1541
|
+
if (options?.pagination?.page) {
|
|
1542
|
+
params.set("page", options.pagination.page.toString());
|
|
1543
|
+
}
|
|
1544
|
+
if (options?.pagination?.limit) {
|
|
1545
|
+
params.set("limit", options.pagination.limit.toString());
|
|
1546
|
+
}
|
|
1547
|
+
if (options?.status) {
|
|
1548
|
+
params.set("status", options.status);
|
|
1549
|
+
}
|
|
1550
|
+
if (options?.sortBy) {
|
|
1551
|
+
params.set("sort_by", options.sortBy);
|
|
1552
|
+
}
|
|
1553
|
+
if (options?.sortDir) {
|
|
1554
|
+
params.set("sort_dir", options.sortDir);
|
|
1555
|
+
}
|
|
1556
|
+
return await client.get(
|
|
1557
|
+
`/dashboards?${params.toString()}`,
|
|
1558
|
+
tenantId,
|
|
1559
|
+
options?.userId,
|
|
1560
|
+
options?.scopes,
|
|
1561
|
+
signal
|
|
1562
|
+
);
|
|
1563
|
+
}
|
|
1564
|
+
async function getDashboard(client, id, options, signal) {
|
|
1565
|
+
const tenantId = resolveTenantId3(client, options?.tenantId);
|
|
1566
|
+
return await client.get(
|
|
1567
|
+
`/dashboards/${id}`,
|
|
1568
|
+
tenantId,
|
|
1569
|
+
options?.userId,
|
|
1570
|
+
options?.scopes,
|
|
1571
|
+
signal
|
|
1572
|
+
);
|
|
1573
|
+
}
|
|
1574
|
+
async function getDashboardForTenant(client, id, tenantId, options, signal) {
|
|
1575
|
+
const params = new URLSearchParams();
|
|
1576
|
+
params.set("tenant_id", tenantId);
|
|
1577
|
+
return await client.get(
|
|
1578
|
+
`/dashboards/${id}/for-tenant?${params.toString()}`,
|
|
1579
|
+
tenantId,
|
|
1580
|
+
options?.userId,
|
|
1581
|
+
options?.scopes,
|
|
1582
|
+
signal
|
|
1583
|
+
);
|
|
1584
|
+
}
|
|
1585
|
+
async function updateDashboard(client, id, body, options, signal) {
|
|
1586
|
+
const tenantId = resolveTenantId3(client, options?.tenantId);
|
|
1587
|
+
return await client.put(
|
|
1588
|
+
`/dashboards/${id}`,
|
|
1589
|
+
body,
|
|
1590
|
+
tenantId,
|
|
1591
|
+
options?.userId,
|
|
1592
|
+
options?.scopes,
|
|
1593
|
+
signal
|
|
1594
|
+
);
|
|
1595
|
+
}
|
|
1596
|
+
async function updateDashboardStatus(client, id, status, options, signal) {
|
|
1597
|
+
const tenantId = resolveTenantId3(client, options?.tenantId);
|
|
1598
|
+
return await client.patch(
|
|
1599
|
+
`/dashboards/${id}/status`,
|
|
1600
|
+
{ status },
|
|
1601
|
+
tenantId,
|
|
1602
|
+
options?.userId,
|
|
1603
|
+
options?.scopes,
|
|
1604
|
+
signal
|
|
1605
|
+
);
|
|
1606
|
+
}
|
|
1607
|
+
async function deleteDashboard(client, id, options, signal) {
|
|
1608
|
+
const tenantId = resolveTenantId3(client, options?.tenantId);
|
|
1609
|
+
await client.delete(
|
|
1610
|
+
`/dashboards/${id}`,
|
|
1611
|
+
tenantId,
|
|
1612
|
+
options?.userId,
|
|
1613
|
+
options?.scopes,
|
|
1614
|
+
signal
|
|
1615
|
+
);
|
|
1616
|
+
}
|
|
1617
|
+
async function forkDashboard(client, id, input, options, signal) {
|
|
1618
|
+
const tenantId = resolveTenantId3(client, options?.tenantId);
|
|
1619
|
+
return await client.post(
|
|
1620
|
+
`/dashboards/${id}/fork`,
|
|
1621
|
+
input,
|
|
1622
|
+
tenantId,
|
|
1623
|
+
options?.userId,
|
|
1624
|
+
options?.scopes,
|
|
1625
|
+
signal
|
|
1626
|
+
);
|
|
1627
|
+
}
|
|
1628
|
+
async function updateFork(client, forkId, input, options, signal) {
|
|
1629
|
+
const tenantId = resolveTenantId3(client, options?.tenantId);
|
|
1630
|
+
return await client.put(
|
|
1631
|
+
`/dashboards/forks/${forkId}`,
|
|
1632
|
+
input,
|
|
1633
|
+
tenantId,
|
|
1634
|
+
options?.userId,
|
|
1635
|
+
options?.scopes,
|
|
1636
|
+
signal
|
|
1637
|
+
);
|
|
1638
|
+
}
|
|
1639
|
+
async function rollbackFork(client, forkId, tenantId, options, signal) {
|
|
1640
|
+
return await client.post(
|
|
1641
|
+
`/dashboards/forks/${forkId}/rollback`,
|
|
1642
|
+
{ tenant_id: tenantId },
|
|
1643
|
+
tenantId,
|
|
1644
|
+
options?.userId,
|
|
1645
|
+
options?.scopes,
|
|
1646
|
+
signal
|
|
1647
|
+
);
|
|
1648
|
+
}
|
|
1649
|
+
async function deleteFork(client, forkId, tenantId, options, signal) {
|
|
1650
|
+
const params = new URLSearchParams();
|
|
1651
|
+
params.set("tenant_id", tenantId);
|
|
1652
|
+
await client.delete(
|
|
1653
|
+
`/dashboards/forks/${forkId}?${params.toString()}`,
|
|
1654
|
+
tenantId,
|
|
1655
|
+
options?.userId,
|
|
1656
|
+
options?.scopes,
|
|
1657
|
+
signal
|
|
1658
|
+
);
|
|
1659
|
+
}
|
|
1660
|
+
async function listForksForTenant(client, tenantId, options, signal) {
|
|
1661
|
+
return await client.get(
|
|
1662
|
+
`/dashboards/customer/${tenantId}`,
|
|
1663
|
+
tenantId,
|
|
1664
|
+
options?.userId,
|
|
1665
|
+
options?.scopes,
|
|
1666
|
+
signal
|
|
1667
|
+
);
|
|
1668
|
+
}
|
|
1212
1669
|
|
|
1213
1670
|
// src/routes/ingest.ts
|
|
1214
1671
|
var import_node_crypto2 = __toESM(require("crypto"), 1);
|
|
1215
1672
|
async function syncSchema(client, queryEngine, databaseName, options, signal) {
|
|
1216
|
-
const tenantId =
|
|
1673
|
+
const tenantId = resolveTenantId4(client, options.tenantId);
|
|
1217
1674
|
const adapter = queryEngine.getDatabase(databaseName);
|
|
1218
1675
|
const metadata = queryEngine.getDatabaseMetadata(databaseName);
|
|
1219
1676
|
const introspection = await adapter.introspect(
|
|
@@ -1235,7 +1692,7 @@ async function syncSchema(client, queryEngine, databaseName, options, signal) {
|
|
|
1235
1692
|
);
|
|
1236
1693
|
return response;
|
|
1237
1694
|
}
|
|
1238
|
-
function
|
|
1695
|
+
function resolveTenantId4(client, tenantId) {
|
|
1239
1696
|
const resolved = tenantId ?? client.getDefaultTenantId();
|
|
1240
1697
|
if (!resolved) {
|
|
1241
1698
|
throw new Error(
|
|
@@ -1277,7 +1734,7 @@ var import_node_crypto4 = __toESM(require("crypto"), 1);
|
|
|
1277
1734
|
// src/routes/query.ts
|
|
1278
1735
|
var import_node_crypto3 = __toESM(require("crypto"), 1);
|
|
1279
1736
|
async function ask(client, queryEngine, question, options, signal) {
|
|
1280
|
-
const tenantId =
|
|
1737
|
+
const tenantId = resolveTenantId5(client, options.tenantId);
|
|
1281
1738
|
const sessionId = import_node_crypto3.default.randomUUID();
|
|
1282
1739
|
const querypanelSessionId = options.querypanelSessionId ?? sessionId;
|
|
1283
1740
|
const maxRetry = options.maxRetry ?? 0;
|
|
@@ -1307,7 +1764,8 @@ async function ask(client, queryEngine, question, options, signal) {
|
|
|
1307
1764
|
...options.maxRetry ? { max_retry: options.maxRetry } : {},
|
|
1308
1765
|
...tenantSettings ? { tenant_settings: tenantSettings } : {},
|
|
1309
1766
|
...databaseName ? { database: databaseName } : {},
|
|
1310
|
-
...metadata?.dialect ? { dialect: metadata.dialect } : {}
|
|
1767
|
+
...metadata?.dialect ? { dialect: metadata.dialect } : {},
|
|
1768
|
+
...options.model?.trim() ? { model: options.model.trim() } : {}
|
|
1311
1769
|
},
|
|
1312
1770
|
tenantId,
|
|
1313
1771
|
options.userId,
|
|
@@ -1331,7 +1789,7 @@ async function ask(client, queryEngine, question, options, signal) {
|
|
|
1331
1789
|
const dbName = queryResponse.data.database ?? options.database ?? queryEngine.getDefaultDatabase();
|
|
1332
1790
|
if (!dbName) {
|
|
1333
1791
|
throw new Error(
|
|
1334
|
-
"No database attached. Call attachPostgres
|
|
1792
|
+
"No database attached. Call attachPostgres, attachClickhouse, or attachBigQuery first."
|
|
1335
1793
|
);
|
|
1336
1794
|
}
|
|
1337
1795
|
const paramMetadata = Array.isArray(queryResponse.data.params) ? queryResponse.data.params : [];
|
|
@@ -1432,7 +1890,7 @@ async function ask(client, queryEngine, question, options, signal) {
|
|
|
1432
1890
|
}
|
|
1433
1891
|
throw new Error("Unexpected error in ask retry loop");
|
|
1434
1892
|
}
|
|
1435
|
-
function
|
|
1893
|
+
function resolveTenantId5(client, tenantId) {
|
|
1436
1894
|
const resolved = tenantId ?? client.getDefaultTenantId();
|
|
1437
1895
|
if (!resolved) {
|
|
1438
1896
|
throw new Error(
|
|
@@ -1588,7 +2046,7 @@ function stripVizSpecOnlyHints(hints) {
|
|
|
1588
2046
|
const { kind: _kind, ...rest } = hints;
|
|
1589
2047
|
return rest;
|
|
1590
2048
|
}
|
|
1591
|
-
function
|
|
2049
|
+
function resolveTenantId6(client, tenantId) {
|
|
1592
2050
|
const resolved = tenantId ?? client.getDefaultTenantId();
|
|
1593
2051
|
if (!resolved) {
|
|
1594
2052
|
throw new Error(
|
|
@@ -1598,7 +2056,7 @@ function resolveTenantId5(client, tenantId) {
|
|
|
1598
2056
|
return resolved;
|
|
1599
2057
|
}
|
|
1600
2058
|
async function modifyChart(client, queryEngine, input, options, signal) {
|
|
1601
|
-
const tenantId =
|
|
2059
|
+
const tenantId = resolveTenantId6(client, options?.tenantId);
|
|
1602
2060
|
const sessionId = import_node_crypto4.default.randomUUID();
|
|
1603
2061
|
const querypanelSessionId = options?.querypanelSessionId ?? sessionId;
|
|
1604
2062
|
const hasQuerypanelSessionContext = !!options?.querypanelSessionId;
|
|
@@ -1766,7 +2224,7 @@ async function modifyChart(client, queryEngine, input, options, signal) {
|
|
|
1766
2224
|
|
|
1767
2225
|
// src/routes/sessions.ts
|
|
1768
2226
|
async function listSessions(client, options, signal) {
|
|
1769
|
-
const tenantId =
|
|
2227
|
+
const tenantId = resolveTenantId7(client, options?.tenantId);
|
|
1770
2228
|
const params = new URLSearchParams();
|
|
1771
2229
|
if (options?.pagination?.page)
|
|
1772
2230
|
params.set("page", `${options.pagination.page}`);
|
|
@@ -1789,7 +2247,7 @@ async function listSessions(client, options, signal) {
|
|
|
1789
2247
|
);
|
|
1790
2248
|
}
|
|
1791
2249
|
async function getSession(client, sessionId, options, signal) {
|
|
1792
|
-
const tenantId =
|
|
2250
|
+
const tenantId = resolveTenantId7(client, options?.tenantId);
|
|
1793
2251
|
const params = new URLSearchParams();
|
|
1794
2252
|
if (options?.includeTurns !== void 0) {
|
|
1795
2253
|
params.set("include_turns", `${options.includeTurns}`);
|
|
@@ -1803,7 +2261,7 @@ async function getSession(client, sessionId, options, signal) {
|
|
|
1803
2261
|
);
|
|
1804
2262
|
}
|
|
1805
2263
|
async function updateSession(client, sessionId, body, options, signal) {
|
|
1806
|
-
const tenantId =
|
|
2264
|
+
const tenantId = resolveTenantId7(client, options?.tenantId);
|
|
1807
2265
|
return await client.patch(
|
|
1808
2266
|
`/sessions/${encodeURIComponent(sessionId)}`,
|
|
1809
2267
|
body,
|
|
@@ -1814,7 +2272,7 @@ async function updateSession(client, sessionId, body, options, signal) {
|
|
|
1814
2272
|
);
|
|
1815
2273
|
}
|
|
1816
2274
|
async function deleteSession(client, sessionId, options, signal) {
|
|
1817
|
-
const tenantId =
|
|
2275
|
+
const tenantId = resolveTenantId7(client, options?.tenantId);
|
|
1818
2276
|
await client.delete(
|
|
1819
2277
|
`/sessions/${encodeURIComponent(sessionId)}`,
|
|
1820
2278
|
tenantId,
|
|
@@ -1823,7 +2281,7 @@ async function deleteSession(client, sessionId, options, signal) {
|
|
|
1823
2281
|
signal
|
|
1824
2282
|
);
|
|
1825
2283
|
}
|
|
1826
|
-
function
|
|
2284
|
+
function resolveTenantId7(client, tenantId) {
|
|
1827
2285
|
const resolved = tenantId ?? client.getDefaultTenantId();
|
|
1828
2286
|
if (!resolved) {
|
|
1829
2287
|
throw new Error(
|
|
@@ -1836,7 +2294,7 @@ function resolveTenantId6(client, tenantId) {
|
|
|
1836
2294
|
// src/routes/vizspec.ts
|
|
1837
2295
|
var import_node_crypto5 = __toESM(require("crypto"), 1);
|
|
1838
2296
|
async function generateVizSpec(client, input, options, signal) {
|
|
1839
|
-
const tenantId =
|
|
2297
|
+
const tenantId = resolveTenantId8(client, options?.tenantId);
|
|
1840
2298
|
const sessionId = import_node_crypto5.default.randomUUID();
|
|
1841
2299
|
const response = await client.post(
|
|
1842
2300
|
"/vizspec",
|
|
@@ -1858,7 +2316,7 @@ async function generateVizSpec(client, input, options, signal) {
|
|
|
1858
2316
|
);
|
|
1859
2317
|
return response;
|
|
1860
2318
|
}
|
|
1861
|
-
function
|
|
2319
|
+
function resolveTenantId8(client, tenantId) {
|
|
1862
2320
|
const resolved = tenantId ?? client.getDefaultTenantId();
|
|
1863
2321
|
if (!resolved) {
|
|
1864
2322
|
throw new Error(
|
|
@@ -1869,13 +2327,64 @@ function resolveTenantId7(client, tenantId) {
|
|
|
1869
2327
|
}
|
|
1870
2328
|
|
|
1871
2329
|
// src/index.ts
|
|
1872
|
-
var QueryPanelSdkAPI = class {
|
|
2330
|
+
var QueryPanelSdkAPI = class _QueryPanelSdkAPI {
|
|
1873
2331
|
client;
|
|
1874
2332
|
queryEngine;
|
|
1875
|
-
|
|
1876
|
-
|
|
2333
|
+
/**
|
|
2334
|
+
* @param workspaceId - Workspace UUID (same value as your org in the dashboard). Passed to the API as JWT claim `organizationId`.
|
|
2335
|
+
*/
|
|
2336
|
+
constructor(baseUrl, privateKey, workspaceId, options) {
|
|
2337
|
+
if (options?.api) {
|
|
2338
|
+
this.client = options.api;
|
|
2339
|
+
} else {
|
|
2340
|
+
if (!baseUrl) throw new Error("Base URL is required");
|
|
2341
|
+
if (!privateKey) throw new Error("Private key is required");
|
|
2342
|
+
if (!workspaceId) throw new Error("Workspace ID is required");
|
|
2343
|
+
this.client = new ApiClient(baseUrl, privateKey, workspaceId, {
|
|
2344
|
+
defaultTenantId: options?.defaultTenantId,
|
|
2345
|
+
additionalHeaders: options?.additionalHeaders,
|
|
2346
|
+
fetch: options?.fetch
|
|
2347
|
+
});
|
|
2348
|
+
}
|
|
1877
2349
|
this.queryEngine = new QueryEngine();
|
|
1878
2350
|
}
|
|
2351
|
+
/**
|
|
2352
|
+
* Create an SDK instance that uses a request callback instead of HTTP.
|
|
2353
|
+
* Use this when calling the SDK from within your own API so that "API" calls
|
|
2354
|
+
* are handled in-process (e.g. by invoking querypanel-sdk services directly)
|
|
2355
|
+
* and do not cause recursion.
|
|
2356
|
+
*
|
|
2357
|
+
* @param workspaceId - Workspace identifier (sent as `organizationId` in JWTs and API auth)
|
|
2358
|
+
* @param requestHandler - Callback invoked for each logical API request (method, path, body, tenantId, etc.)
|
|
2359
|
+
* @param options - Optional defaultTenantId
|
|
2360
|
+
* @returns QueryPanelSdkAPI instance; attach databases and call ask(), syncSchema(), etc. as usual
|
|
2361
|
+
*
|
|
2362
|
+
* @example
|
|
2363
|
+
* ```ts
|
|
2364
|
+
* const qp = QueryPanelSdkAPI.withCallbacks(
|
|
2365
|
+
* process.env.QUERYPANEL_WORKSPACE_ID!,
|
|
2366
|
+
* async (opts) => {
|
|
2367
|
+
* // Call your in-process services (e.g. querypanel-sdk logic) instead of HTTP
|
|
2368
|
+
* if (opts.path === '/query' || opts.path === '/v2/query') {
|
|
2369
|
+
* const result = await yourSqlGenerator.generate(opts.body, opts.tenantId);
|
|
2370
|
+
* return { data: result, headers: new Headers({ 'x-querypanel-session-id': result.sessionId }) };
|
|
2371
|
+
* }
|
|
2372
|
+
* if (opts.path === '/chart') return { data: await yourChartService.generate(opts.body) };
|
|
2373
|
+
* // ... other paths
|
|
2374
|
+
* return { data: await yourProxy(opts) };
|
|
2375
|
+
* },
|
|
2376
|
+
* { defaultTenantId: process.env.DEFAULT_TENANT_ID }
|
|
2377
|
+
* );
|
|
2378
|
+
* qp.attachPostgres('db', createClient, { tenantFieldName: 'tenant_id' });
|
|
2379
|
+
* const result = await qp.ask('Top 10 orders', { tenantId: 't1', database: 'db' });
|
|
2380
|
+
* ```
|
|
2381
|
+
*/
|
|
2382
|
+
static withCallbacks(workspaceId, requestHandler, options) {
|
|
2383
|
+
const api = new CallbackApiClient(requestHandler, {
|
|
2384
|
+
defaultTenantId: options?.defaultTenantId
|
|
2385
|
+
});
|
|
2386
|
+
return new _QueryPanelSdkAPI("", "", workspaceId, { api });
|
|
2387
|
+
}
|
|
1879
2388
|
// Database attachment methods
|
|
1880
2389
|
attachClickhouse(name, clientFn, options) {
|
|
1881
2390
|
const adapter = new ClickHouseAdapter(clientFn, options);
|
|
@@ -1903,6 +2412,19 @@ var QueryPanelSdkAPI = class {
|
|
|
1903
2412
|
};
|
|
1904
2413
|
this.queryEngine.attachDatabase(name, adapter, metadata);
|
|
1905
2414
|
}
|
|
2415
|
+
attachBigQuery(name, clientFn, options) {
|
|
2416
|
+
const adapter = new BigQueryAdapter(clientFn, options);
|
|
2417
|
+
const metadata = {
|
|
2418
|
+
name,
|
|
2419
|
+
dialect: "bigquery",
|
|
2420
|
+
description: options?.description,
|
|
2421
|
+
tags: options?.tags,
|
|
2422
|
+
tenantFieldName: options?.tenantFieldName,
|
|
2423
|
+
tenantFieldType: options?.tenantFieldType ?? "String",
|
|
2424
|
+
enforceTenantIsolation: options?.tenantFieldName ? options?.enforceTenantIsolation ?? true : void 0
|
|
2425
|
+
};
|
|
2426
|
+
this.queryEngine.attachDatabase(name, adapter, metadata);
|
|
2427
|
+
}
|
|
1906
2428
|
attachDatabase(name, adapter) {
|
|
1907
2429
|
const metadata = {
|
|
1908
2430
|
name,
|
|
@@ -1989,6 +2511,48 @@ var QueryPanelSdkAPI = class {
|
|
|
1989
2511
|
signal
|
|
1990
2512
|
);
|
|
1991
2513
|
}
|
|
2514
|
+
// Embedded dashboard / JWT
|
|
2515
|
+
/**
|
|
2516
|
+
* Creates a JWT for the given tenant (and optional userId, scopes).
|
|
2517
|
+
* Use this when you need to pass a token to the embed (e.g. frontend or demo).
|
|
2518
|
+
* Only available when using the HTTP client (ApiClient); not available when using withCallbacks.
|
|
2519
|
+
*/
|
|
2520
|
+
async createJwt(options) {
|
|
2521
|
+
const tenantId = options.tenantId?.trim();
|
|
2522
|
+
if (!tenantId) {
|
|
2523
|
+
throw new Error("tenantId is required");
|
|
2524
|
+
}
|
|
2525
|
+
if (!(this.client instanceof ApiClient)) {
|
|
2526
|
+
throw new Error(
|
|
2527
|
+
"createJwt is not available when using withCallbacks. Use the SDK with baseUrl + privateKey to create JWTs."
|
|
2528
|
+
);
|
|
2529
|
+
}
|
|
2530
|
+
return await this.client.createJwt(
|
|
2531
|
+
tenantId,
|
|
2532
|
+
options.userId,
|
|
2533
|
+
options.scopes
|
|
2534
|
+
);
|
|
2535
|
+
}
|
|
2536
|
+
/**
|
|
2537
|
+
* Runs SQL against an attached database with tenant isolation applied.
|
|
2538
|
+
* Used by embed run-sql (e.g. dashboard chart execution). Requires a database
|
|
2539
|
+
* to be attached (e.g. via attachPostgres) and options.tenantId.
|
|
2540
|
+
*/
|
|
2541
|
+
async runSqlForDashboard(input, options) {
|
|
2542
|
+
const tenantId = options.tenantId?.trim();
|
|
2543
|
+
if (!tenantId) {
|
|
2544
|
+
throw new Error("tenantId is required");
|
|
2545
|
+
}
|
|
2546
|
+
const databaseName = input.database ?? "db";
|
|
2547
|
+
const params = input.params ?? {};
|
|
2548
|
+
const result = await this.queryEngine.validateAndExecute(
|
|
2549
|
+
input.sql,
|
|
2550
|
+
params,
|
|
2551
|
+
databaseName,
|
|
2552
|
+
tenantId
|
|
2553
|
+
);
|
|
2554
|
+
return { rows: result.rows, fields: result.fields };
|
|
2555
|
+
}
|
|
1992
2556
|
// VizSpec generation
|
|
1993
2557
|
/**
|
|
1994
2558
|
* Generates a VizSpec visualization specification from query results.
|
|
@@ -2432,9 +2996,298 @@ var QueryPanelSdkAPI = class {
|
|
|
2432
2996
|
async deleteActiveChart(id, options, signal) {
|
|
2433
2997
|
await deleteActiveChart(this.client, id, options, signal);
|
|
2434
2998
|
}
|
|
2999
|
+
// Dashboard CRUD operations
|
|
3000
|
+
/**
|
|
3001
|
+
* Creates a new dashboard with BlockNote content.
|
|
3002
|
+
*
|
|
3003
|
+
* @param body - Dashboard configuration including name and BlockNote content
|
|
3004
|
+
* @param options - Tenant, user, and scope options
|
|
3005
|
+
* @param signal - Optional AbortSignal for cancellation
|
|
3006
|
+
* @returns Created dashboard
|
|
3007
|
+
*
|
|
3008
|
+
* @example
|
|
3009
|
+
* ```typescript
|
|
3010
|
+
* const dashboard = await qp.createDashboard({
|
|
3011
|
+
* name: "Sales Dashboard",
|
|
3012
|
+
* description: "Monthly sales metrics",
|
|
3013
|
+
* content_json: blockNoteContent,
|
|
3014
|
+
* editor_type: "blocknote",
|
|
3015
|
+
* }, { tenantId: "tenant_123" });
|
|
3016
|
+
* ```
|
|
3017
|
+
*/
|
|
3018
|
+
async createDashboard(body, options, signal) {
|
|
3019
|
+
return await createDashboard(
|
|
3020
|
+
this.client,
|
|
3021
|
+
body,
|
|
3022
|
+
options,
|
|
3023
|
+
signal
|
|
3024
|
+
);
|
|
3025
|
+
}
|
|
3026
|
+
/**
|
|
3027
|
+
* Lists dashboards with pagination and filtering.
|
|
3028
|
+
*
|
|
3029
|
+
* @param options - Filtering and pagination options
|
|
3030
|
+
* @param signal - Optional AbortSignal for cancellation
|
|
3031
|
+
* @returns Paginated list of dashboards
|
|
3032
|
+
*
|
|
3033
|
+
* @example
|
|
3034
|
+
* ```typescript
|
|
3035
|
+
* const dashboards = await qp.listDashboards({
|
|
3036
|
+
* tenantId: "tenant_123",
|
|
3037
|
+
* status: "deployed",
|
|
3038
|
+
* pagination: { page: 1, limit: 10 },
|
|
3039
|
+
* });
|
|
3040
|
+
* ```
|
|
3041
|
+
*/
|
|
3042
|
+
async listDashboards(options, signal) {
|
|
3043
|
+
return await listDashboards(this.client, options, signal);
|
|
3044
|
+
}
|
|
3045
|
+
/**
|
|
3046
|
+
* Gets a dashboard by ID.
|
|
3047
|
+
*
|
|
3048
|
+
* @param id - Dashboard ID
|
|
3049
|
+
* @param options - Tenant, user, and scope options
|
|
3050
|
+
* @param signal - Optional AbortSignal for cancellation
|
|
3051
|
+
* @returns Dashboard details
|
|
3052
|
+
*
|
|
3053
|
+
* @example
|
|
3054
|
+
* ```typescript
|
|
3055
|
+
* const dashboard = await qp.getDashboard("dash_123", {
|
|
3056
|
+
* tenantId: "tenant_123",
|
|
3057
|
+
* });
|
|
3058
|
+
* ```
|
|
3059
|
+
*/
|
|
3060
|
+
async getDashboard(id, options, signal) {
|
|
3061
|
+
return await getDashboard(
|
|
3062
|
+
this.client,
|
|
3063
|
+
id,
|
|
3064
|
+
options,
|
|
3065
|
+
signal
|
|
3066
|
+
);
|
|
3067
|
+
}
|
|
3068
|
+
/**
|
|
3069
|
+
* Gets a dashboard for a specific tenant.
|
|
3070
|
+
* Returns customer fork if exists, otherwise returns the original dashboard.
|
|
3071
|
+
*
|
|
3072
|
+
* @param id - Dashboard ID
|
|
3073
|
+
* @param tenantId - Tenant ID to check for fork
|
|
3074
|
+
* @param options - Additional auth options
|
|
3075
|
+
* @param signal - Optional AbortSignal for cancellation
|
|
3076
|
+
* @returns Dashboard (fork if exists, original otherwise)
|
|
3077
|
+
*
|
|
3078
|
+
* @example
|
|
3079
|
+
* ```typescript
|
|
3080
|
+
* const dashboard = await qp.getDashboardForTenant(
|
|
3081
|
+
* "dash_123",
|
|
3082
|
+
* "tenant_456",
|
|
3083
|
+
* );
|
|
3084
|
+
* ```
|
|
3085
|
+
*/
|
|
3086
|
+
async getDashboardForTenant(id, tenantId, options, signal) {
|
|
3087
|
+
return await getDashboardForTenant(
|
|
3088
|
+
this.client,
|
|
3089
|
+
id,
|
|
3090
|
+
tenantId,
|
|
3091
|
+
options,
|
|
3092
|
+
signal
|
|
3093
|
+
);
|
|
3094
|
+
}
|
|
3095
|
+
/**
|
|
3096
|
+
* Updates a dashboard.
|
|
3097
|
+
*
|
|
3098
|
+
* @param id - Dashboard ID to update
|
|
3099
|
+
* @param body - Fields to update
|
|
3100
|
+
* @param options - Tenant, user, and scope options
|
|
3101
|
+
* @param signal - Optional AbortSignal for cancellation
|
|
3102
|
+
* @returns Updated dashboard
|
|
3103
|
+
*
|
|
3104
|
+
* @example
|
|
3105
|
+
* ```typescript
|
|
3106
|
+
* const updated = await qp.updateDashboard("dash_123", {
|
|
3107
|
+
* name: "Updated Dashboard",
|
|
3108
|
+
* content_json: newBlockNoteContent,
|
|
3109
|
+
* }, { tenantId: "tenant_123" });
|
|
3110
|
+
* ```
|
|
3111
|
+
*/
|
|
3112
|
+
async updateDashboard(id, body, options, signal) {
|
|
3113
|
+
return await updateDashboard(
|
|
3114
|
+
this.client,
|
|
3115
|
+
id,
|
|
3116
|
+
body,
|
|
3117
|
+
options,
|
|
3118
|
+
signal
|
|
3119
|
+
);
|
|
3120
|
+
}
|
|
3121
|
+
/**
|
|
3122
|
+
* Updates dashboard status (deploy/undeploy).
|
|
3123
|
+
*
|
|
3124
|
+
* @param id - Dashboard ID
|
|
3125
|
+
* @param status - New status
|
|
3126
|
+
* @param options - Tenant, user, and scope options
|
|
3127
|
+
* @param signal - Optional AbortSignal for cancellation
|
|
3128
|
+
* @returns Updated dashboard
|
|
3129
|
+
*
|
|
3130
|
+
* @example
|
|
3131
|
+
* ```typescript
|
|
3132
|
+
* const deployed = await qp.updateDashboardStatus(
|
|
3133
|
+
* "dash_123",
|
|
3134
|
+
* "deployed",
|
|
3135
|
+
* { tenantId: "tenant_123" },
|
|
3136
|
+
* );
|
|
3137
|
+
* ```
|
|
3138
|
+
*/
|
|
3139
|
+
async updateDashboardStatus(id, status, options, signal) {
|
|
3140
|
+
return await updateDashboardStatus(
|
|
3141
|
+
this.client,
|
|
3142
|
+
id,
|
|
3143
|
+
status,
|
|
3144
|
+
options,
|
|
3145
|
+
signal
|
|
3146
|
+
);
|
|
3147
|
+
}
|
|
3148
|
+
/**
|
|
3149
|
+
* Deletes a dashboard.
|
|
3150
|
+
*
|
|
3151
|
+
* @param id - Dashboard ID to delete
|
|
3152
|
+
* @param options - Tenant, user, and scope options
|
|
3153
|
+
* @param signal - Optional AbortSignal for cancellation
|
|
3154
|
+
*
|
|
3155
|
+
* @example
|
|
3156
|
+
* ```typescript
|
|
3157
|
+
* await qp.deleteDashboard("dash_123", { tenantId: "tenant_123" });
|
|
3158
|
+
* ```
|
|
3159
|
+
*/
|
|
3160
|
+
async deleteDashboard(id, options, signal) {
|
|
3161
|
+
await deleteDashboard(this.client, id, options, signal);
|
|
3162
|
+
}
|
|
3163
|
+
// Dashboard Fork operations (Customer Customization)
|
|
3164
|
+
/**
|
|
3165
|
+
* Forks a dashboard for customer customization (copy-on-write).
|
|
3166
|
+
*
|
|
3167
|
+
* Creates a full copy of the dashboard that the customer can edit independently.
|
|
3168
|
+
* The original dashboard remains unchanged.
|
|
3169
|
+
*
|
|
3170
|
+
* @param id - Dashboard ID to fork
|
|
3171
|
+
* @param input - Fork configuration with tenant_id
|
|
3172
|
+
* @param options - Additional auth options
|
|
3173
|
+
* @param signal - Optional AbortSignal for cancellation
|
|
3174
|
+
* @returns Forked dashboard
|
|
3175
|
+
*
|
|
3176
|
+
* @example
|
|
3177
|
+
* ```typescript
|
|
3178
|
+
* const fork = await qp.forkDashboard("dash_123", {
|
|
3179
|
+
* tenant_id: "tenant_456",
|
|
3180
|
+
* name: "Customer's Custom Dashboard",
|
|
3181
|
+
* });
|
|
3182
|
+
* ```
|
|
3183
|
+
*/
|
|
3184
|
+
async forkDashboard(id, input, options, signal) {
|
|
3185
|
+
return await forkDashboard(
|
|
3186
|
+
this.client,
|
|
3187
|
+
id,
|
|
3188
|
+
input,
|
|
3189
|
+
options,
|
|
3190
|
+
signal
|
|
3191
|
+
);
|
|
3192
|
+
}
|
|
3193
|
+
/**
|
|
3194
|
+
* Updates a customer fork.
|
|
3195
|
+
*
|
|
3196
|
+
* @param forkId - Fork ID to update
|
|
3197
|
+
* @param input - Update data including tenant_id
|
|
3198
|
+
* @param options - Additional auth options
|
|
3199
|
+
* @param signal - Optional AbortSignal for cancellation
|
|
3200
|
+
* @returns Updated fork
|
|
3201
|
+
*
|
|
3202
|
+
* @example
|
|
3203
|
+
* ```typescript
|
|
3204
|
+
* const updated = await qp.updateFork("fork_123", {
|
|
3205
|
+
* tenant_id: "tenant_456",
|
|
3206
|
+
* content_json: newBlockNoteContent,
|
|
3207
|
+
* });
|
|
3208
|
+
* ```
|
|
3209
|
+
*/
|
|
3210
|
+
async updateFork(forkId, input, options, signal) {
|
|
3211
|
+
return await updateFork(
|
|
3212
|
+
this.client,
|
|
3213
|
+
forkId,
|
|
3214
|
+
input,
|
|
3215
|
+
options,
|
|
3216
|
+
signal
|
|
3217
|
+
);
|
|
3218
|
+
}
|
|
3219
|
+
/**
|
|
3220
|
+
* Rollbacks a fork to the original dashboard.
|
|
3221
|
+
* This deletes the fork and the customer will see the original again.
|
|
3222
|
+
*
|
|
3223
|
+
* @param forkId - Fork ID to rollback
|
|
3224
|
+
* @param tenantId - Tenant ID
|
|
3225
|
+
* @param options - Additional auth options
|
|
3226
|
+
* @param signal - Optional AbortSignal for cancellation
|
|
3227
|
+
* @returns Original dashboard
|
|
3228
|
+
*
|
|
3229
|
+
* @example
|
|
3230
|
+
* ```typescript
|
|
3231
|
+
* const original = await qp.rollbackFork("fork_123", "tenant_456");
|
|
3232
|
+
* ```
|
|
3233
|
+
*/
|
|
3234
|
+
async rollbackFork(forkId, tenantId, options, signal) {
|
|
3235
|
+
return await rollbackFork(
|
|
3236
|
+
this.client,
|
|
3237
|
+
forkId,
|
|
3238
|
+
tenantId,
|
|
3239
|
+
options,
|
|
3240
|
+
signal
|
|
3241
|
+
);
|
|
3242
|
+
}
|
|
3243
|
+
/**
|
|
3244
|
+
* Deletes a customer fork.
|
|
3245
|
+
*
|
|
3246
|
+
* @param forkId - Fork ID to delete
|
|
3247
|
+
* @param tenantId - Tenant ID
|
|
3248
|
+
* @param options - Additional auth options
|
|
3249
|
+
* @param signal - Optional AbortSignal for cancellation
|
|
3250
|
+
*
|
|
3251
|
+
* @example
|
|
3252
|
+
* ```typescript
|
|
3253
|
+
* await qp.deleteFork("fork_123", "tenant_456");
|
|
3254
|
+
* ```
|
|
3255
|
+
*/
|
|
3256
|
+
async deleteFork(forkId, tenantId, options, signal) {
|
|
3257
|
+
await deleteFork(
|
|
3258
|
+
this.client,
|
|
3259
|
+
forkId,
|
|
3260
|
+
tenantId,
|
|
3261
|
+
options,
|
|
3262
|
+
signal
|
|
3263
|
+
);
|
|
3264
|
+
}
|
|
3265
|
+
/**
|
|
3266
|
+
* Lists all customer forks for a tenant.
|
|
3267
|
+
*
|
|
3268
|
+
* @param tenantId - Tenant ID
|
|
3269
|
+
* @param options - Additional auth options
|
|
3270
|
+
* @param signal - Optional AbortSignal for cancellation
|
|
3271
|
+
* @returns Array of forks
|
|
3272
|
+
*
|
|
3273
|
+
* @example
|
|
3274
|
+
* ```typescript
|
|
3275
|
+
* const forks = await qp.listForksForTenant("tenant_456");
|
|
3276
|
+
* ```
|
|
3277
|
+
*/
|
|
3278
|
+
async listForksForTenant(tenantId, options, signal) {
|
|
3279
|
+
return await listForksForTenant(
|
|
3280
|
+
this.client,
|
|
3281
|
+
tenantId,
|
|
3282
|
+
options,
|
|
3283
|
+
signal
|
|
3284
|
+
);
|
|
3285
|
+
}
|
|
2435
3286
|
};
|
|
2436
3287
|
// Annotate the CommonJS export names for ESM import in node:
|
|
2437
3288
|
0 && (module.exports = {
|
|
3289
|
+
BigQueryAdapter,
|
|
3290
|
+
CallbackApiClient,
|
|
2438
3291
|
ClickHouseAdapter,
|
|
2439
3292
|
PostgresAdapter,
|
|
2440
3293
|
QueryErrorCode,
|