yamchart 0.10.2 → 0.10.3
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/dist/{advisor-5F73GF54.js → advisor-XTJQU7E4.js} +12 -12
- package/dist/{chunk-5CRXV5PQ.js → chunk-2XJZJ6IA.js} +2 -2
- package/dist/{chunk-X3UG6T3Q.js → chunk-6ELSQNIH.js} +2 -2
- package/dist/{chunk-UOSOZT47.js → chunk-KDDO4UII.js} +57 -5
- package/dist/chunk-KDDO4UII.js.map +1 -0
- package/dist/{chunk-25ES7MGF.js → chunk-ODSPBOJW.js} +4 -4
- package/dist/{chunk-7HWFVUGP.js → chunk-OYBI433Z.js} +59 -2
- package/dist/chunk-OYBI433Z.js.map +1 -0
- package/dist/{chunk-WVSDQNAN.js → chunk-PXFTWXSV.js} +2 -2
- package/dist/{chunk-PYVROL7Z.js → chunk-XNMB25GT.js} +4 -4
- package/dist/{connection-utils-HNX3473B.js → connection-utils-S7EFPTLY.js} +5 -5
- package/dist/{describe-KDGXFPDQ.js → describe-TA2CTODC.js} +6 -6
- package/dist/{dev-KOWFGDOP.js → dev-PHFYLJPJ.js} +1336 -796
- package/dist/dev-PHFYLJPJ.js.map +1 -0
- package/dist/{dist-WMP7BFKP.js → dist-F6TVLP6C.js} +12 -4
- package/dist/{dist-YB5RBWCC.js → dist-Q5ETETUU.js} +4 -4
- package/dist/{dist-LK3ZG74Q.js → dist-YJOOH65Y.js} +9 -5
- package/dist/index.js +26 -26
- package/dist/public/assets/DataView-V-E6oxy6.js +9 -0
- package/dist/public/assets/{EventManagement-BIkVUSCe.js → EventManagement-Do2xU5lV.js} +1 -1
- package/dist/public/assets/{ExplorePage-CeaAulqT.js → ExplorePage-Dg50mvGB.js} +1 -1
- package/dist/public/assets/{LoginPage-4WeNcdRi.js → LoginPage-DmYGIXcu.js} +1 -1
- package/dist/public/assets/{PublicViewer-CkW-G56D.js → PublicViewer-CJTgc9OF.js} +1 -1
- package/dist/public/assets/{SetupWizard-CwTr3PCy.js → SetupWizard-Bo-zmh43.js} +1 -1
- package/dist/public/assets/{ShareManagement-DSon6CoC.js → ShareManagement-6Wp6b2Gp.js} +1 -1
- package/dist/public/assets/{SourceDetailView-BA4XVKqV.js → SourceDetailView-CTzSXhZc.js} +1 -1
- package/dist/public/assets/{UserManagement-BWD2Ugkv.js → UserManagement-DQhB13T0.js} +1 -1
- package/dist/public/assets/{index-iR9gLdl9.js → index-CY8xsfQH.js} +31 -31
- package/dist/public/assets/{index-Cb4omrVn.css → index-Dkn-tJCT.css} +1 -1
- package/dist/public/assets/{index.es-BuEtjxhO.js → index.es-Bc3yys5L.js} +1 -1
- package/dist/public/assets/{jspdf.es.min-CScx2_oK.js → jspdf.es.min-D94JEH-J.js} +3 -3
- package/dist/public/index.html +2 -2
- package/dist/{query-7XHKSZ5H.js → query-AX4BOPMH.js} +5 -5
- package/dist/{sample-YNSJ3I3T.js → sample-4NUBJGIL.js} +5 -5
- package/dist/{search-NKRXXLRS.js → search-IBVVYN4I.js} +6 -6
- package/dist/{semantic-NBAX4WU4.js → semantic-33YIUMUV.js} +3 -3
- package/dist/{semantic-DD7P4634.js → semantic-5WGVZ7HY.js} +3 -3
- package/dist/{source-resolver-7NY3KEZ2.js → source-resolver-GYXIRIVK.js} +6 -6
- package/dist/{sync-warehouse-PK7HXW7U.js → sync-warehouse-5NKENCOZ.js} +5 -5
- package/dist/{tables-WQTRZSXR.js → tables-QMZMYYV5.js} +6 -6
- package/dist/{test-MKNKUR3N.js → test-6MCPGXJP.js} +5 -5
- package/package.json +5 -5
- package/dist/chunk-7HWFVUGP.js.map +0 -1
- package/dist/chunk-UOSOZT47.js.map +0 -1
- package/dist/dev-KOWFGDOP.js.map +0 -1
- package/dist/public/assets/DataView-BfwnWnLH.js +0 -9
- /package/dist/{advisor-5F73GF54.js.map → advisor-XTJQU7E4.js.map} +0 -0
- /package/dist/{chunk-5CRXV5PQ.js.map → chunk-2XJZJ6IA.js.map} +0 -0
- /package/dist/{chunk-X3UG6T3Q.js.map → chunk-6ELSQNIH.js.map} +0 -0
- /package/dist/{chunk-25ES7MGF.js.map → chunk-ODSPBOJW.js.map} +0 -0
- /package/dist/{chunk-WVSDQNAN.js.map → chunk-PXFTWXSV.js.map} +0 -0
- /package/dist/{chunk-PYVROL7Z.js.map → chunk-XNMB25GT.js.map} +0 -0
- /package/dist/{connection-utils-HNX3473B.js.map → connection-utils-S7EFPTLY.js.map} +0 -0
- /package/dist/{describe-KDGXFPDQ.js.map → describe-TA2CTODC.js.map} +0 -0
- /package/dist/{dist-LK3ZG74Q.js.map → dist-F6TVLP6C.js.map} +0 -0
- /package/dist/{dist-WMP7BFKP.js.map → dist-Q5ETETUU.js.map} +0 -0
- /package/dist/{dist-YB5RBWCC.js.map → dist-YJOOH65Y.js.map} +0 -0
- /package/dist/{query-7XHKSZ5H.js.map → query-AX4BOPMH.js.map} +0 -0
- /package/dist/{sample-YNSJ3I3T.js.map → sample-4NUBJGIL.js.map} +0 -0
- /package/dist/{search-NKRXXLRS.js.map → search-IBVVYN4I.js.map} +0 -0
- /package/dist/{semantic-NBAX4WU4.js.map → semantic-33YIUMUV.js.map} +0 -0
- /package/dist/{semantic-DD7P4634.js.map → semantic-5WGVZ7HY.js.map} +0 -0
- /package/dist/{source-resolver-7NY3KEZ2.js.map → source-resolver-GYXIRIVK.js.map} +0 -0
- /package/dist/{sync-warehouse-PK7HXW7U.js.map → sync-warehouse-5NKENCOZ.js.map} +0 -0
- /package/dist/{tables-WQTRZSXR.js.map → tables-QMZMYYV5.js.map} +0 -0
- /package/dist/{test-MKNKUR3N.js.map → test-6MCPGXJP.js.map} +0 -0
|
@@ -2,7 +2,7 @@ import { createRequire as __yamchartCreateRequire } from 'module'; if (!globalTh
|
|
|
2
2
|
import {
|
|
3
3
|
loadEnvFile,
|
|
4
4
|
validateProject
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-ODSPBOJW.js";
|
|
6
6
|
import {
|
|
7
7
|
box,
|
|
8
8
|
detail,
|
|
@@ -18,10 +18,16 @@ import {
|
|
|
18
18
|
filterCatalogEntries
|
|
19
19
|
} from "./chunk-ZMJPRNOA.js";
|
|
20
20
|
import {
|
|
21
|
+
buildModelPath,
|
|
21
22
|
buildProvider,
|
|
22
23
|
createEmbedder,
|
|
23
|
-
createProviderForTask
|
|
24
|
-
|
|
24
|
+
createProviderForTask,
|
|
25
|
+
detectConventions,
|
|
26
|
+
formatModelSql,
|
|
27
|
+
scaffoldModelSql,
|
|
28
|
+
updateSchemaYml,
|
|
29
|
+
writeDbtModel
|
|
30
|
+
} from "./chunk-KDDO4UII.js";
|
|
25
31
|
import {
|
|
26
32
|
BigQueryConnector,
|
|
27
33
|
DuckDBConnector,
|
|
@@ -40,7 +46,7 @@ import {
|
|
|
40
46
|
resolveMySQLAuth,
|
|
41
47
|
resolvePostgresAuth,
|
|
42
48
|
resolveSnowflakeAuth
|
|
43
|
-
} from "./chunk-
|
|
49
|
+
} from "./chunk-2XJZJ6IA.js";
|
|
44
50
|
import {
|
|
45
51
|
SemanticModelBuilder,
|
|
46
52
|
SemanticQueryCompiler,
|
|
@@ -57,7 +63,7 @@ import {
|
|
|
57
63
|
isRelativeDateRange,
|
|
58
64
|
loadSemanticDefinitions,
|
|
59
65
|
validateSemanticQuery
|
|
60
|
-
} from "./chunk-
|
|
66
|
+
} from "./chunk-6ELSQNIH.js";
|
|
61
67
|
import {
|
|
62
68
|
AiConfigSchema,
|
|
63
69
|
ChartSchema,
|
|
@@ -74,7 +80,7 @@ import {
|
|
|
74
80
|
SemanticQuerySchema,
|
|
75
81
|
deepMerge,
|
|
76
82
|
resolveProjectConfig
|
|
77
|
-
} from "./chunk-
|
|
83
|
+
} from "./chunk-OYBI433Z.js";
|
|
78
84
|
import {
|
|
79
85
|
AuthDatabase,
|
|
80
86
|
generateOpaqueToken,
|
|
@@ -3040,7 +3046,7 @@ var require_compile = __commonJS({
|
|
|
3040
3046
|
const schOrFunc = root.refs[ref];
|
|
3041
3047
|
if (schOrFunc)
|
|
3042
3048
|
return schOrFunc;
|
|
3043
|
-
let _sch =
|
|
3049
|
+
let _sch = resolve4.call(this, root, ref);
|
|
3044
3050
|
if (_sch === void 0) {
|
|
3045
3051
|
const schema = (_a = root.localRefs) === null || _a === void 0 ? void 0 : _a[ref];
|
|
3046
3052
|
const { schemaId } = this.opts;
|
|
@@ -3067,7 +3073,7 @@ var require_compile = __commonJS({
|
|
|
3067
3073
|
function sameSchemaEnv(s1, s2) {
|
|
3068
3074
|
return s1.schema === s2.schema && s1.root === s2.root && s1.baseId === s2.baseId;
|
|
3069
3075
|
}
|
|
3070
|
-
function
|
|
3076
|
+
function resolve4(root, ref) {
|
|
3071
3077
|
let sch;
|
|
3072
3078
|
while (typeof (sch = this.refs[ref]) == "string")
|
|
3073
3079
|
ref = sch;
|
|
@@ -3642,7 +3648,7 @@ var require_fast_uri = __commonJS({
|
|
|
3642
3648
|
}
|
|
3643
3649
|
return uri;
|
|
3644
3650
|
}
|
|
3645
|
-
function
|
|
3651
|
+
function resolve4(baseURI, relativeURI, options) {
|
|
3646
3652
|
const schemelessOptions = options ? Object.assign({ scheme: "null" }, options) : { scheme: "null" };
|
|
3647
3653
|
const resolved = resolveComponent(parse2(baseURI, schemelessOptions), parse2(relativeURI, schemelessOptions), schemelessOptions, true);
|
|
3648
3654
|
schemelessOptions.skipEscape = true;
|
|
@@ -3869,7 +3875,7 @@ var require_fast_uri = __commonJS({
|
|
|
3869
3875
|
var fastUri = {
|
|
3870
3876
|
SCHEMES,
|
|
3871
3877
|
normalize,
|
|
3872
|
-
resolve:
|
|
3878
|
+
resolve: resolve4,
|
|
3873
3879
|
resolveComponent,
|
|
3874
3880
|
equal,
|
|
3875
3881
|
serialize,
|
|
@@ -6850,7 +6856,7 @@ var require_dist = __commonJS({
|
|
|
6850
6856
|
});
|
|
6851
6857
|
|
|
6852
6858
|
// src/commands/dev.ts
|
|
6853
|
-
import { resolve as
|
|
6859
|
+
import { resolve as resolve3 } from "path";
|
|
6854
6860
|
import { homedir } from "os";
|
|
6855
6861
|
|
|
6856
6862
|
// ../server/dist/server.js
|
|
@@ -6858,9 +6864,476 @@ import Fastify from "fastify";
|
|
|
6858
6864
|
import cors from "@fastify/cors";
|
|
6859
6865
|
import fastifyStatic from "@fastify/static";
|
|
6860
6866
|
|
|
6861
|
-
//
|
|
6867
|
+
// ../../packages/engine/dist/cache.js
|
|
6868
|
+
import { LRUCache } from "lru-cache";
|
|
6869
|
+
var MemoryCache = class {
|
|
6870
|
+
cache;
|
|
6871
|
+
defaultTtlMs;
|
|
6872
|
+
hits = 0;
|
|
6873
|
+
misses = 0;
|
|
6874
|
+
constructor(options) {
|
|
6875
|
+
this.defaultTtlMs = options.defaultTtlMs;
|
|
6876
|
+
this.cache = new LRUCache({
|
|
6877
|
+
max: options.maxSize,
|
|
6878
|
+
ttl: options.defaultTtlMs,
|
|
6879
|
+
ttlAutopurge: true
|
|
6880
|
+
});
|
|
6881
|
+
}
|
|
6882
|
+
async get(key) {
|
|
6883
|
+
const value = this.cache.get(key);
|
|
6884
|
+
if (value) {
|
|
6885
|
+
this.hits++;
|
|
6886
|
+
return value;
|
|
6887
|
+
}
|
|
6888
|
+
this.misses++;
|
|
6889
|
+
return null;
|
|
6890
|
+
}
|
|
6891
|
+
async set(key, value, ttlMs) {
|
|
6892
|
+
this.cache.set(key, value, { ttl: ttlMs ?? this.defaultTtlMs });
|
|
6893
|
+
}
|
|
6894
|
+
async invalidate(key) {
|
|
6895
|
+
this.cache.delete(key);
|
|
6896
|
+
}
|
|
6897
|
+
async invalidatePattern(pattern) {
|
|
6898
|
+
const regexPattern = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*");
|
|
6899
|
+
const regex = new RegExp(`^${regexPattern}$`);
|
|
6900
|
+
for (const key of this.cache.keys()) {
|
|
6901
|
+
if (regex.test(key)) {
|
|
6902
|
+
this.cache.delete(key);
|
|
6903
|
+
}
|
|
6904
|
+
}
|
|
6905
|
+
}
|
|
6906
|
+
async clear() {
|
|
6907
|
+
this.cache.clear();
|
|
6908
|
+
}
|
|
6909
|
+
getStats() {
|
|
6910
|
+
return {
|
|
6911
|
+
size: this.cache.size,
|
|
6912
|
+
hits: this.hits,
|
|
6913
|
+
misses: this.misses
|
|
6914
|
+
};
|
|
6915
|
+
}
|
|
6916
|
+
};
|
|
6917
|
+
function parseTtl2(ttl) {
|
|
6918
|
+
const match = ttl.match(/^(\d+)(ms|s|m|h|d)$/);
|
|
6919
|
+
if (!match) {
|
|
6920
|
+
throw new Error(`Invalid TTL format: ${ttl}`);
|
|
6921
|
+
}
|
|
6922
|
+
const value = parseInt(match[1] ?? "0", 10);
|
|
6923
|
+
const unit = match[2];
|
|
6924
|
+
switch (unit) {
|
|
6925
|
+
case "ms":
|
|
6926
|
+
return value;
|
|
6927
|
+
case "s":
|
|
6928
|
+
return value * 1e3;
|
|
6929
|
+
case "m":
|
|
6930
|
+
return value * 60 * 1e3;
|
|
6931
|
+
case "h":
|
|
6932
|
+
return value * 60 * 60 * 1e3;
|
|
6933
|
+
case "d":
|
|
6934
|
+
return value * 24 * 60 * 60 * 1e3;
|
|
6935
|
+
default:
|
|
6936
|
+
throw new Error(`Unknown TTL unit: ${unit}`);
|
|
6937
|
+
}
|
|
6938
|
+
}
|
|
6939
|
+
|
|
6940
|
+
// ../../packages/engine/dist/connector-factory.js
|
|
6941
|
+
import { join } from "path";
|
|
6942
|
+
function createConnectorFromConfig(connection, options) {
|
|
6943
|
+
const { projectDir } = options;
|
|
6944
|
+
switch (connection.type) {
|
|
6945
|
+
case "duckdb": {
|
|
6946
|
+
const duckdbConfig = connection;
|
|
6947
|
+
const dbPath = duckdbConfig.config.path.startsWith("/") ? duckdbConfig.config.path : join(projectDir, duckdbConfig.config.path);
|
|
6948
|
+
return new DuckDBConnector({ path: dbPath });
|
|
6949
|
+
}
|
|
6950
|
+
case "postgres": {
|
|
6951
|
+
const pgConnection = connection;
|
|
6952
|
+
const credentials = resolvePostgresAuth(pgConnection, options.secrets);
|
|
6953
|
+
return new PostgresConnector({
|
|
6954
|
+
host: pgConnection.config.host,
|
|
6955
|
+
port: pgConnection.config.port,
|
|
6956
|
+
database: pgConnection.config.database,
|
|
6957
|
+
schema: pgConnection.config.schema,
|
|
6958
|
+
ssl: pgConnection.config.ssl,
|
|
6959
|
+
user: credentials.user,
|
|
6960
|
+
password: credentials.password,
|
|
6961
|
+
min: pgConnection.pool?.min_connections,
|
|
6962
|
+
max: pgConnection.pool?.max_connections,
|
|
6963
|
+
idleTimeoutMillis: pgConnection.pool?.idle_timeout,
|
|
6964
|
+
statementTimeout: pgConnection.query?.timeout
|
|
6965
|
+
});
|
|
6966
|
+
}
|
|
6967
|
+
case "mysql": {
|
|
6968
|
+
const mysqlConnection = connection;
|
|
6969
|
+
const credentials = resolveMySQLAuth(mysqlConnection, options.secrets);
|
|
6970
|
+
return new MySQLConnector({
|
|
6971
|
+
host: mysqlConnection.config.host,
|
|
6972
|
+
port: mysqlConnection.config.port,
|
|
6973
|
+
database: mysqlConnection.config.database,
|
|
6974
|
+
ssl: mysqlConnection.config.ssl,
|
|
6975
|
+
user: credentials.user,
|
|
6976
|
+
password: credentials.password,
|
|
6977
|
+
min: mysqlConnection.pool?.min_connections,
|
|
6978
|
+
max: mysqlConnection.pool?.max_connections,
|
|
6979
|
+
idleTimeoutMillis: mysqlConnection.pool?.idle_timeout,
|
|
6980
|
+
statementTimeout: mysqlConnection.query?.timeout
|
|
6981
|
+
});
|
|
6982
|
+
}
|
|
6983
|
+
case "sqlite": {
|
|
6984
|
+
const sqliteConnection = connection;
|
|
6985
|
+
const dbPath = sqliteConnection.config.path.startsWith("/") ? sqliteConnection.config.path : sqliteConnection.config.path === ":memory:" ? ":memory:" : join(projectDir, sqliteConnection.config.path);
|
|
6986
|
+
return new SQLiteConnector({ path: dbPath });
|
|
6987
|
+
}
|
|
6988
|
+
case "snowflake": {
|
|
6989
|
+
const sfConnection = connection;
|
|
6990
|
+
const credentials = resolveSnowflakeAuth(sfConnection, options.secrets);
|
|
6991
|
+
return new SnowflakeConnector({
|
|
6992
|
+
account: sfConnection.config.account,
|
|
6993
|
+
username: credentials.username,
|
|
6994
|
+
password: credentials.password,
|
|
6995
|
+
privateKey: credentials.privateKey,
|
|
6996
|
+
authenticator: credentials.authenticator,
|
|
6997
|
+
clientStoreTemporaryCredential: credentials.clientStoreTemporaryCredential,
|
|
6998
|
+
warehouse: sfConnection.config.warehouse,
|
|
6999
|
+
database: sfConnection.config.database,
|
|
7000
|
+
schema: sfConnection.config.schema,
|
|
7001
|
+
role: sfConnection.config.role,
|
|
7002
|
+
statementTimeout: sfConnection.query?.timeout
|
|
7003
|
+
});
|
|
7004
|
+
}
|
|
7005
|
+
case "bigquery": {
|
|
7006
|
+
const bqConnection = connection;
|
|
7007
|
+
const creds = resolveBigQueryAuth(bqConnection, options.secrets);
|
|
7008
|
+
const keyFilename = creds.keyFilename ? creds.keyFilename.startsWith("/") ? creds.keyFilename : join(projectDir, creds.keyFilename) : void 0;
|
|
7009
|
+
return new BigQueryConnector({
|
|
7010
|
+
projectId: bqConnection.config.project_id,
|
|
7011
|
+
dataset: bqConnection.config.dataset,
|
|
7012
|
+
location: bqConnection.config.location,
|
|
7013
|
+
maxBytesBilled: bqConnection.config.max_bytes_billed,
|
|
7014
|
+
credentials: creds.credentials,
|
|
7015
|
+
keyFilename
|
|
7016
|
+
});
|
|
7017
|
+
}
|
|
7018
|
+
default:
|
|
7019
|
+
throw new Error(`Unsupported connection type: ${connection.type}`);
|
|
7020
|
+
}
|
|
7021
|
+
}
|
|
7022
|
+
function buildConnectionInfo(connection) {
|
|
7023
|
+
const info2 = {
|
|
7024
|
+
name: connection.name,
|
|
7025
|
+
type: connection.type
|
|
7026
|
+
};
|
|
7027
|
+
switch (connection.type) {
|
|
7028
|
+
case "duckdb": {
|
|
7029
|
+
const c = connection;
|
|
7030
|
+
info2.path = c.config.path;
|
|
7031
|
+
break;
|
|
7032
|
+
}
|
|
7033
|
+
case "postgres": {
|
|
7034
|
+
const c = connection;
|
|
7035
|
+
info2.host = c.config.host;
|
|
7036
|
+
info2.port = c.config.port;
|
|
7037
|
+
info2.database = c.config.database;
|
|
7038
|
+
info2.schema = c.config.schema;
|
|
7039
|
+
break;
|
|
7040
|
+
}
|
|
7041
|
+
case "mysql": {
|
|
7042
|
+
const c = connection;
|
|
7043
|
+
info2.host = c.config.host;
|
|
7044
|
+
info2.port = c.config.port;
|
|
7045
|
+
info2.database = c.config.database;
|
|
7046
|
+
break;
|
|
7047
|
+
}
|
|
7048
|
+
case "sqlite": {
|
|
7049
|
+
const c = connection;
|
|
7050
|
+
info2.path = c.config.path;
|
|
7051
|
+
break;
|
|
7052
|
+
}
|
|
7053
|
+
case "snowflake": {
|
|
7054
|
+
const c = connection;
|
|
7055
|
+
info2.account = c.config.account;
|
|
7056
|
+
info2.warehouse = c.config.warehouse;
|
|
7057
|
+
info2.database = c.config.database;
|
|
7058
|
+
info2.schema = c.config.schema;
|
|
7059
|
+
info2.role = c.config.role;
|
|
7060
|
+
break;
|
|
7061
|
+
}
|
|
7062
|
+
case "bigquery": {
|
|
7063
|
+
const c = connection;
|
|
7064
|
+
info2.database = c.config.project_id;
|
|
7065
|
+
info2.schema = c.config.dataset;
|
|
7066
|
+
break;
|
|
7067
|
+
}
|
|
7068
|
+
}
|
|
7069
|
+
return info2;
|
|
7070
|
+
}
|
|
7071
|
+
async function testConnection(connection, options) {
|
|
7072
|
+
const start = performance.now();
|
|
7073
|
+
let connector = null;
|
|
7074
|
+
try {
|
|
7075
|
+
connector = createConnectorFromConfig(connection, options);
|
|
7076
|
+
await connector.connect();
|
|
7077
|
+
const healthQuery = connection.type === "duckdb" ? "SELECT 1 AS health" : "SELECT 1";
|
|
7078
|
+
await connector.execute(healthQuery);
|
|
7079
|
+
const latencyMs = performance.now() - start;
|
|
7080
|
+
return {
|
|
7081
|
+
name: connection.name,
|
|
7082
|
+
type: connection.type,
|
|
7083
|
+
status: "healthy",
|
|
7084
|
+
latencyMs: Math.round(latencyMs)
|
|
7085
|
+
};
|
|
7086
|
+
} catch (err) {
|
|
7087
|
+
const latencyMs = performance.now() - start;
|
|
7088
|
+
let errorMessage = "Unknown error";
|
|
7089
|
+
if (err instanceof Error) {
|
|
7090
|
+
const pgError = err;
|
|
7091
|
+
errorMessage = err.message || pgError.code || "Connection failed";
|
|
7092
|
+
} else {
|
|
7093
|
+
errorMessage = String(err);
|
|
7094
|
+
}
|
|
7095
|
+
return {
|
|
7096
|
+
name: connection.name,
|
|
7097
|
+
type: connection.type,
|
|
7098
|
+
status: "unhealthy",
|
|
7099
|
+
latencyMs: Math.round(latencyMs),
|
|
7100
|
+
error: errorMessage
|
|
7101
|
+
};
|
|
7102
|
+
} finally {
|
|
7103
|
+
if (connector) {
|
|
7104
|
+
try {
|
|
7105
|
+
await connector.disconnect();
|
|
7106
|
+
} catch {
|
|
7107
|
+
}
|
|
7108
|
+
}
|
|
7109
|
+
}
|
|
7110
|
+
}
|
|
7111
|
+
|
|
7112
|
+
// ../../packages/engine/dist/query-service.js
|
|
7113
|
+
function quoteIdentifier(name, dialect) {
|
|
7114
|
+
if (dialect === "mysql" || dialect === "bigquery")
|
|
7115
|
+
return `\`${name.replace(/`/g, "``")}\``;
|
|
7116
|
+
return `"${name.replace(/"/g, '""')}"`;
|
|
7117
|
+
}
|
|
7118
|
+
var QueryService = class {
|
|
7119
|
+
compiler;
|
|
7120
|
+
connector;
|
|
7121
|
+
cache;
|
|
7122
|
+
models;
|
|
7123
|
+
refs;
|
|
7124
|
+
dialect;
|
|
7125
|
+
constructor(config) {
|
|
7126
|
+
this.compiler = new QueryCompiler({
|
|
7127
|
+
models: config.models,
|
|
7128
|
+
refs: config.refs
|
|
7129
|
+
});
|
|
7130
|
+
this.connector = config.connector;
|
|
7131
|
+
this.cache = config.cache;
|
|
7132
|
+
this.models = new Map(Object.entries(config.models));
|
|
7133
|
+
this.refs = config.refs;
|
|
7134
|
+
this.dialect = config.dialect ?? "duckdb";
|
|
7135
|
+
}
|
|
7136
|
+
async executeChart(chart, params, userContext) {
|
|
7137
|
+
const compiled = this.compiler.compile(chart, params, userContext);
|
|
7138
|
+
const cached = await this.cache.get(compiled.cacheKey);
|
|
7139
|
+
if (cached) {
|
|
7140
|
+
return {
|
|
7141
|
+
columns: cached.columns,
|
|
7142
|
+
rows: cached.rows,
|
|
7143
|
+
rowCount: cached.rowCount,
|
|
7144
|
+
durationMs: cached.durationMs,
|
|
7145
|
+
cached: true,
|
|
7146
|
+
cacheKey: compiled.cacheKey,
|
|
7147
|
+
compiledSql: compiled.sql
|
|
7148
|
+
};
|
|
7149
|
+
}
|
|
7150
|
+
const result = await this.connector.execute(compiled.sql);
|
|
7151
|
+
const cacheEntry = {
|
|
7152
|
+
columns: result.columns,
|
|
7153
|
+
rows: result.rows,
|
|
7154
|
+
rowCount: result.rowCount,
|
|
7155
|
+
durationMs: result.durationMs,
|
|
7156
|
+
cachedAt: Date.now()
|
|
7157
|
+
};
|
|
7158
|
+
await this.cache.set(compiled.cacheKey, cacheEntry);
|
|
7159
|
+
return {
|
|
7160
|
+
...result,
|
|
7161
|
+
cached: false,
|
|
7162
|
+
cacheKey: compiled.cacheKey,
|
|
7163
|
+
compiledSql: compiled.sql
|
|
7164
|
+
};
|
|
7165
|
+
}
|
|
7166
|
+
/**
|
|
7167
|
+
* Execute a table chart with server-side pagination.
|
|
7168
|
+
* Wraps the compiled SQL with LIMIT/OFFSET and runs a parallel COUNT(*) query.
|
|
7169
|
+
* Optionally computes summary aggregations for specified columns.
|
|
7170
|
+
*/
|
|
7171
|
+
async executeChartPaginated(chart, params, options, userContext) {
|
|
7172
|
+
const compiled = this.compiler.compile(chart, params, userContext);
|
|
7173
|
+
const baseSql = compiled.sql;
|
|
7174
|
+
const q = quoteIdentifier;
|
|
7175
|
+
const d = this.dialect;
|
|
7176
|
+
let orderBy = "";
|
|
7177
|
+
if (options.sortField) {
|
|
7178
|
+
const cleanField = options.sortField.replace(/[^\w\s]/g, "");
|
|
7179
|
+
if (cleanField.length > 0) {
|
|
7180
|
+
orderBy = `ORDER BY ${q(cleanField, d)} ${options.sortDirection === "desc" ? "DESC" : "ASC"}`;
|
|
7181
|
+
}
|
|
7182
|
+
}
|
|
7183
|
+
const offset = options.page * options.pageSize;
|
|
7184
|
+
const dataSql = `SELECT * FROM (${baseSql}) _t ${orderBy} LIMIT ${options.pageSize} OFFSET ${offset}`;
|
|
7185
|
+
const countCacheKey = `${compiled.cacheKey}:count`;
|
|
7186
|
+
let totalCount;
|
|
7187
|
+
const cachedCount = await this.cache.get(countCacheKey);
|
|
7188
|
+
if (cachedCount) {
|
|
7189
|
+
totalCount = cachedCount.rowCount;
|
|
7190
|
+
} else {
|
|
7191
|
+
const countSql = `SELECT COUNT(*) AS _count FROM (${baseSql}) _t`;
|
|
7192
|
+
const countResult = await this.connector.execute(countSql);
|
|
7193
|
+
totalCount = Number(countResult.rows[0]?._count ?? 0);
|
|
7194
|
+
await this.cache.set(countCacheKey, {
|
|
7195
|
+
columns: [],
|
|
7196
|
+
rows: [],
|
|
7197
|
+
rowCount: totalCount,
|
|
7198
|
+
durationMs: countResult.durationMs,
|
|
7199
|
+
cachedAt: Date.now()
|
|
7200
|
+
});
|
|
7201
|
+
}
|
|
7202
|
+
let summaryValues;
|
|
7203
|
+
if (options.summaryColumns && options.summaryColumns.length > 0) {
|
|
7204
|
+
const summaryCacheKey = `${compiled.cacheKey}:summary`;
|
|
7205
|
+
const cachedSummary = await this.cache.get(summaryCacheKey);
|
|
7206
|
+
if (cachedSummary && cachedSummary.rows[0]) {
|
|
7207
|
+
summaryValues = cachedSummary.rows[0];
|
|
7208
|
+
} else {
|
|
7209
|
+
const aggExprs = options.summaryColumns.map(({ field, summary }) => {
|
|
7210
|
+
const col = q(field, d);
|
|
7211
|
+
switch (summary) {
|
|
7212
|
+
case "sum":
|
|
7213
|
+
return `SUM(${col}) AS ${q(`_sum_${field}`, d)}`;
|
|
7214
|
+
case "avg":
|
|
7215
|
+
return `AVG(${col}) AS ${q(`_avg_${field}`, d)}`;
|
|
7216
|
+
case "min":
|
|
7217
|
+
return `MIN(${col}) AS ${q(`_min_${field}`, d)}`;
|
|
7218
|
+
case "max":
|
|
7219
|
+
return `MAX(${col}) AS ${q(`_max_${field}`, d)}`;
|
|
7220
|
+
case "count":
|
|
7221
|
+
return `COUNT(${col}) AS ${q(`_count_${field}`, d)}`;
|
|
7222
|
+
default:
|
|
7223
|
+
return null;
|
|
7224
|
+
}
|
|
7225
|
+
}).filter(Boolean);
|
|
7226
|
+
if (aggExprs.length > 0) {
|
|
7227
|
+
const summarySql = `SELECT ${aggExprs.join(", ")} FROM (${baseSql}) _t`;
|
|
7228
|
+
const summaryResult = await this.connector.execute(summarySql);
|
|
7229
|
+
if (summaryResult.rows[0]) {
|
|
7230
|
+
summaryValues = {};
|
|
7231
|
+
for (const { field, summary } of options.summaryColumns) {
|
|
7232
|
+
const key = `_${summary}_${field}`;
|
|
7233
|
+
summaryValues[`${summary}:${field}`] = Number(summaryResult.rows[0][key] ?? 0);
|
|
7234
|
+
}
|
|
7235
|
+
await this.cache.set(summaryCacheKey, {
|
|
7236
|
+
columns: [],
|
|
7237
|
+
rows: [summaryValues],
|
|
7238
|
+
rowCount: 0,
|
|
7239
|
+
durationMs: summaryResult.durationMs,
|
|
7240
|
+
cachedAt: Date.now()
|
|
7241
|
+
});
|
|
7242
|
+
}
|
|
7243
|
+
}
|
|
7244
|
+
}
|
|
7245
|
+
}
|
|
7246
|
+
const result = await this.connector.execute(dataSql);
|
|
7247
|
+
return {
|
|
7248
|
+
...result,
|
|
7249
|
+
cached: false,
|
|
7250
|
+
cacheKey: compiled.cacheKey,
|
|
7251
|
+
compiledSql: dataSql,
|
|
7252
|
+
totalCount,
|
|
7253
|
+
page: options.page,
|
|
7254
|
+
pageSize: options.pageSize,
|
|
7255
|
+
summaryValues
|
|
7256
|
+
};
|
|
7257
|
+
}
|
|
7258
|
+
/**
|
|
7259
|
+
* Execute a chart query for CSV export with a bounded row limit.
|
|
7260
|
+
* Returns full QueryResult — caller streams to HTTP response.
|
|
7261
|
+
*/
|
|
7262
|
+
async executeChartForExport(chart, params, maxRows, userContext) {
|
|
7263
|
+
const compiled = this.compiler.compile(chart, params, userContext);
|
|
7264
|
+
const sql = `SELECT * FROM (${compiled.sql}) _t LIMIT ${maxRows}`;
|
|
7265
|
+
const result = await this.connector.execute(sql);
|
|
7266
|
+
return {
|
|
7267
|
+
...result,
|
|
7268
|
+
cached: false,
|
|
7269
|
+
cacheKey: compiled.cacheKey,
|
|
7270
|
+
compiledSql: sql
|
|
7271
|
+
};
|
|
7272
|
+
}
|
|
7273
|
+
/**
|
|
7274
|
+
* Compile a chart query without executing it.
|
|
7275
|
+
* Returns the compiled SQL and cache key.
|
|
7276
|
+
*/
|
|
7277
|
+
compileChart(chart, params, userContext) {
|
|
7278
|
+
return this.compiler.compile(chart, params, userContext);
|
|
7279
|
+
}
|
|
7280
|
+
invalidateChart(chartName) {
|
|
7281
|
+
this.cache.invalidatePattern(`${chartName}:*`);
|
|
7282
|
+
}
|
|
7283
|
+
invalidateAll() {
|
|
7284
|
+
this.cache.clear();
|
|
7285
|
+
}
|
|
7286
|
+
/**
|
|
7287
|
+
* Update the compiler with new models and refs.
|
|
7288
|
+
* Called when config is reloaded.
|
|
7289
|
+
*/
|
|
7290
|
+
updateCompiler(config) {
|
|
7291
|
+
this.compiler = new QueryCompiler({
|
|
7292
|
+
models: config.models,
|
|
7293
|
+
refs: config.refs
|
|
7294
|
+
});
|
|
7295
|
+
this.models = new Map(Object.entries(config.models));
|
|
7296
|
+
this.refs = config.refs;
|
|
7297
|
+
}
|
|
7298
|
+
getCacheStats() {
|
|
7299
|
+
return this.cache.getStats();
|
|
7300
|
+
}
|
|
7301
|
+
async executeRawSql(sql) {
|
|
7302
|
+
return this.connector.execute(sql);
|
|
7303
|
+
}
|
|
7304
|
+
async executeModel(modelName, params, userContext, limit2 = 1e3, filters) {
|
|
7305
|
+
const model = this.models.get(modelName);
|
|
7306
|
+
if (!model) {
|
|
7307
|
+
throw new Error(`Unknown model: ${modelName}`);
|
|
7308
|
+
}
|
|
7309
|
+
const context = createTemplateContext(params, this.refs, userContext);
|
|
7310
|
+
let sql = renderTemplate(model.sql, context);
|
|
7311
|
+
if (filters && Object.keys(filters).length > 0) {
|
|
7312
|
+
const clauses = Object.entries(filters).map(([field, value]) => `${field} = '${value.replace(/'/g, "''")}'`);
|
|
7313
|
+
sql = `SELECT * FROM (${sql}) _detail WHERE ${clauses.join(" AND ")} LIMIT ${limit2}`;
|
|
7314
|
+
} else {
|
|
7315
|
+
sql = `SELECT * FROM (${sql}) _detail LIMIT ${limit2}`;
|
|
7316
|
+
}
|
|
7317
|
+
return this.connector.execute(sql);
|
|
7318
|
+
}
|
|
7319
|
+
async executeParameterOptions(modelName, valueField, labelField) {
|
|
7320
|
+
const model = this.models.get(modelName);
|
|
7321
|
+
if (!model) {
|
|
7322
|
+
throw new Error(`Unknown model: ${modelName}`);
|
|
7323
|
+
}
|
|
7324
|
+
const context = createTemplateContext({}, this.refs);
|
|
7325
|
+
const sql = renderTemplate(model.sql, context);
|
|
7326
|
+
const result = await this.connector.execute(sql);
|
|
7327
|
+
return result.rows.map((row) => ({
|
|
7328
|
+
value: String(row[valueField] ?? ""),
|
|
7329
|
+
label: String(row[labelField] ?? row[valueField] ?? "")
|
|
7330
|
+
}));
|
|
7331
|
+
}
|
|
7332
|
+
};
|
|
7333
|
+
|
|
7334
|
+
// ../../packages/engine/dist/config-loader.js
|
|
6862
7335
|
import { readFile, readdir, access } from "fs/promises";
|
|
6863
|
-
import { join, extname, relative, dirname } from "path";
|
|
7336
|
+
import { join as join2, extname, relative, dirname } from "path";
|
|
6864
7337
|
import { parse as parseYaml } from "yaml";
|
|
6865
7338
|
import { watch } from "chokidar";
|
|
6866
7339
|
|
|
@@ -7113,7 +7586,7 @@ data: ${JSON.stringify(event.data)}
|
|
|
7113
7586
|
`;
|
|
7114
7587
|
}
|
|
7115
7588
|
|
|
7116
|
-
//
|
|
7589
|
+
// ../../packages/engine/dist/config-loader.js
|
|
7117
7590
|
var ConfigLoader = class {
|
|
7118
7591
|
projectDir;
|
|
7119
7592
|
project = null;
|
|
@@ -7164,7 +7637,7 @@ var ConfigLoader = class {
|
|
|
7164
7637
|
await this.loadAgents();
|
|
7165
7638
|
}
|
|
7166
7639
|
async loadProject() {
|
|
7167
|
-
const projectPath =
|
|
7640
|
+
const projectPath = join2(this.projectDir, "yamchart.yaml");
|
|
7168
7641
|
try {
|
|
7169
7642
|
await access(projectPath);
|
|
7170
7643
|
} catch {
|
|
@@ -7173,7 +7646,7 @@ var ConfigLoader = class {
|
|
|
7173
7646
|
const content = await readFile(projectPath, "utf-8");
|
|
7174
7647
|
const raw = parseYaml(content);
|
|
7175
7648
|
let merged = resolveProjectConfig(raw, this.env);
|
|
7176
|
-
const localPath =
|
|
7649
|
+
const localPath = join2(this.projectDir, "yamchart.local.yaml");
|
|
7177
7650
|
try {
|
|
7178
7651
|
await access(localPath);
|
|
7179
7652
|
const localContent = await readFile(localPath, "utf-8");
|
|
@@ -7190,7 +7663,7 @@ var ConfigLoader = class {
|
|
|
7190
7663
|
this.project = result.data;
|
|
7191
7664
|
}
|
|
7192
7665
|
async loadConnections() {
|
|
7193
|
-
const connectionsDir =
|
|
7666
|
+
const connectionsDir = join2(this.projectDir, "connections");
|
|
7194
7667
|
try {
|
|
7195
7668
|
await access(connectionsDir);
|
|
7196
7669
|
} catch {
|
|
@@ -7200,7 +7673,7 @@ var ConfigLoader = class {
|
|
|
7200
7673
|
for (const file of files) {
|
|
7201
7674
|
if (extname(file) !== ".yaml" && extname(file) !== ".yml")
|
|
7202
7675
|
continue;
|
|
7203
|
-
const filePath =
|
|
7676
|
+
const filePath = join2(connectionsDir, file);
|
|
7204
7677
|
const content = await readFile(filePath, "utf-8");
|
|
7205
7678
|
const parsed = parseYaml(content);
|
|
7206
7679
|
const result = ConnectionSchema.safeParse(parsed);
|
|
@@ -7212,7 +7685,7 @@ var ConfigLoader = class {
|
|
|
7212
7685
|
}
|
|
7213
7686
|
}
|
|
7214
7687
|
async loadCharts() {
|
|
7215
|
-
const chartsDir =
|
|
7688
|
+
const chartsDir = join2(this.projectDir, "charts");
|
|
7216
7689
|
try {
|
|
7217
7690
|
await access(chartsDir);
|
|
7218
7691
|
} catch {
|
|
@@ -7223,7 +7696,7 @@ var ConfigLoader = class {
|
|
|
7223
7696
|
async loadChartsFromDir(dir, baseDir) {
|
|
7224
7697
|
const entries = await readdir(dir, { withFileTypes: true });
|
|
7225
7698
|
for (const entry of entries) {
|
|
7226
|
-
const fullPath =
|
|
7699
|
+
const fullPath = join2(dir, entry.name);
|
|
7227
7700
|
if (entry.isDirectory() && dir === baseDir) {
|
|
7228
7701
|
await this.loadChartsFromDir(fullPath, baseDir);
|
|
7229
7702
|
} else if (entry.isFile() && (extname(entry.name) === ".yaml" || extname(entry.name) === ".yml")) {
|
|
@@ -7256,7 +7729,7 @@ var ConfigLoader = class {
|
|
|
7256
7729
|
}
|
|
7257
7730
|
}
|
|
7258
7731
|
async loadModels() {
|
|
7259
|
-
const modelsDir =
|
|
7732
|
+
const modelsDir = join2(this.projectDir, "models");
|
|
7260
7733
|
try {
|
|
7261
7734
|
await access(modelsDir);
|
|
7262
7735
|
} catch {
|
|
@@ -7267,7 +7740,7 @@ var ConfigLoader = class {
|
|
|
7267
7740
|
async loadModelsFromDir(dir) {
|
|
7268
7741
|
const entries = await readdir(dir, { withFileTypes: true });
|
|
7269
7742
|
for (const entry of entries) {
|
|
7270
|
-
const fullPath =
|
|
7743
|
+
const fullPath = join2(dir, entry.name);
|
|
7271
7744
|
if (entry.isDirectory()) {
|
|
7272
7745
|
await this.loadModelsFromDir(fullPath);
|
|
7273
7746
|
} else if (extname(entry.name) === ".sql") {
|
|
@@ -7286,7 +7759,7 @@ var ConfigLoader = class {
|
|
|
7286
7759
|
}
|
|
7287
7760
|
}
|
|
7288
7761
|
async loadDashboards() {
|
|
7289
|
-
const dashboardsDir =
|
|
7762
|
+
const dashboardsDir = join2(this.projectDir, "dashboards");
|
|
7290
7763
|
try {
|
|
7291
7764
|
await access(dashboardsDir);
|
|
7292
7765
|
} catch {
|
|
@@ -7297,7 +7770,7 @@ var ConfigLoader = class {
|
|
|
7297
7770
|
async loadDashboardsFromDir(dir, baseDir) {
|
|
7298
7771
|
const entries = await readdir(dir, { withFileTypes: true });
|
|
7299
7772
|
for (const entry of entries) {
|
|
7300
|
-
const fullPath =
|
|
7773
|
+
const fullPath = join2(dir, entry.name);
|
|
7301
7774
|
if (entry.isDirectory() && dir === baseDir) {
|
|
7302
7775
|
await this.loadDashboardsFromDir(fullPath, baseDir);
|
|
7303
7776
|
} else if (entry.isFile() && (extname(entry.name) === ".yaml" || extname(entry.name) === ".yml")) {
|
|
@@ -7330,7 +7803,7 @@ var ConfigLoader = class {
|
|
|
7330
7803
|
}
|
|
7331
7804
|
}
|
|
7332
7805
|
async loadSchedules() {
|
|
7333
|
-
const schedulesDir =
|
|
7806
|
+
const schedulesDir = join2(this.projectDir, "schedules");
|
|
7334
7807
|
try {
|
|
7335
7808
|
await access(schedulesDir);
|
|
7336
7809
|
} catch {
|
|
@@ -7340,7 +7813,7 @@ var ConfigLoader = class {
|
|
|
7340
7813
|
for (const file of files) {
|
|
7341
7814
|
if (extname(file) !== ".yaml" && extname(file) !== ".yml")
|
|
7342
7815
|
continue;
|
|
7343
|
-
const filePath =
|
|
7816
|
+
const filePath = join2(schedulesDir, file);
|
|
7344
7817
|
const content = await readFile(filePath, "utf-8");
|
|
7345
7818
|
const parsed = parseYaml(content);
|
|
7346
7819
|
const result = ScheduleSchema.safeParse(parsed);
|
|
@@ -7352,7 +7825,7 @@ var ConfigLoader = class {
|
|
|
7352
7825
|
}
|
|
7353
7826
|
}
|
|
7354
7827
|
async loadEvents() {
|
|
7355
|
-
const eventsPath =
|
|
7828
|
+
const eventsPath = join2(this.projectDir, "events.yaml");
|
|
7356
7829
|
try {
|
|
7357
7830
|
await access(eventsPath);
|
|
7358
7831
|
} catch {
|
|
@@ -7368,12 +7841,12 @@ var ConfigLoader = class {
|
|
|
7368
7841
|
}
|
|
7369
7842
|
}
|
|
7370
7843
|
async loadAgents() {
|
|
7371
|
-
const agentsDir =
|
|
7844
|
+
const agentsDir = join2(this.projectDir, "agents");
|
|
7372
7845
|
this.agentRegistry.clearProjectAgents();
|
|
7373
7846
|
try {
|
|
7374
7847
|
const files = await readdir(agentsDir);
|
|
7375
7848
|
for (const file of files.filter((f) => f.endsWith(".yaml") || f.endsWith(".yml"))) {
|
|
7376
|
-
const content = await readFile(
|
|
7849
|
+
const content = await readFile(join2(agentsDir, file), "utf-8");
|
|
7377
7850
|
this.agentRegistry.loadFromYaml(content, `agents/${file}`, (msg) => {
|
|
7378
7851
|
console.warn(msg);
|
|
7379
7852
|
});
|
|
@@ -7385,15 +7858,15 @@ var ConfigLoader = class {
|
|
|
7385
7858
|
if (this.watcher)
|
|
7386
7859
|
return;
|
|
7387
7860
|
const watchPaths = [
|
|
7388
|
-
|
|
7389
|
-
|
|
7390
|
-
|
|
7391
|
-
|
|
7392
|
-
|
|
7393
|
-
|
|
7394
|
-
|
|
7395
|
-
|
|
7396
|
-
|
|
7861
|
+
join2(this.projectDir, "yamchart.yaml"),
|
|
7862
|
+
join2(this.projectDir, "yamchart.local.yaml"),
|
|
7863
|
+
join2(this.projectDir, "connections"),
|
|
7864
|
+
join2(this.projectDir, "charts"),
|
|
7865
|
+
join2(this.projectDir, "models"),
|
|
7866
|
+
join2(this.projectDir, "dashboards"),
|
|
7867
|
+
join2(this.projectDir, "schedules"),
|
|
7868
|
+
join2(this.projectDir, "events.yaml"),
|
|
7869
|
+
join2(this.projectDir, "agents")
|
|
7397
7870
|
];
|
|
7398
7871
|
this.watcher = watch(watchPaths, {
|
|
7399
7872
|
ignored: /(^|[\/\\])\../,
|
|
@@ -7469,692 +7942,231 @@ Fix: run "ulimit -n 10240" before starting yamchart dev, or add it to your shell
|
|
|
7469
7942
|
this.runtimeKnowledgeUrl = rc.knowledge?.database_url;
|
|
7470
7943
|
this.runtimeAi = rc.ai;
|
|
7471
7944
|
}
|
|
7472
|
-
getConnectionSecrets(name) {
|
|
7473
|
-
return this.connectionSecrets.get(name);
|
|
7474
|
-
}
|
|
7475
|
-
/** Knowledge DB URL: runtime override wins, else the named env var. */
|
|
7476
|
-
getKnowledgeDbUrl(envVarName, env) {
|
|
7477
|
-
return this.runtimeKnowledgeUrl ?? env[envVarName];
|
|
7478
|
-
}
|
|
7479
|
-
/** Effective AI config: Git project.ai with the runtime ai deep-merged over it. */
|
|
7480
|
-
getAiConfig() {
|
|
7481
|
-
const base2 = this.project?.ai;
|
|
7482
|
-
if (!this.runtimeAi)
|
|
7483
|
-
return base2;
|
|
7484
|
-
return deepMerge(base2 ?? {}, this.runtimeAi);
|
|
7485
|
-
}
|
|
7486
|
-
getCharts() {
|
|
7487
|
-
return Array.from(this.charts.values());
|
|
7488
|
-
}
|
|
7489
|
-
getChartByName(name) {
|
|
7490
|
-
return this.charts.get(name);
|
|
7491
|
-
}
|
|
7492
|
-
getChartFolder(name) {
|
|
7493
|
-
return this.chartFolders.get(name) ?? "";
|
|
7494
|
-
}
|
|
7495
|
-
getChartFilePath(name) {
|
|
7496
|
-
return this.chartFilePaths.get(name);
|
|
7497
|
-
}
|
|
7498
|
-
getModels() {
|
|
7499
|
-
return Array.from(this.models.values());
|
|
7500
|
-
}
|
|
7501
|
-
getModelByName(name) {
|
|
7502
|
-
return this.models.get(name);
|
|
7503
|
-
}
|
|
7504
|
-
getDefaultConnection() {
|
|
7505
|
-
const envConnection = process.env.YAMCHART_CONNECTION;
|
|
7506
|
-
if (envConnection) {
|
|
7507
|
-
return this.connections.get(envConnection);
|
|
7508
|
-
}
|
|
7509
|
-
const defaultName = this.project?.defaults?.connection;
|
|
7510
|
-
if (defaultName) {
|
|
7511
|
-
return this.connections.get(defaultName);
|
|
7512
|
-
}
|
|
7513
|
-
return this.connections.values().next().value;
|
|
7514
|
-
}
|
|
7515
|
-
getDashboards() {
|
|
7516
|
-
return Array.from(this.dashboards.values());
|
|
7517
|
-
}
|
|
7518
|
-
getDashboardByName(name) {
|
|
7519
|
-
return this.dashboards.get(name);
|
|
7520
|
-
}
|
|
7521
|
-
getDashboardFolder(name) {
|
|
7522
|
-
return this.dashboardFolders.get(name) ?? "";
|
|
7523
|
-
}
|
|
7524
|
-
getDashboardFilePath(name) {
|
|
7525
|
-
return this.dashboardFilePaths.get(name);
|
|
7526
|
-
}
|
|
7527
|
-
getSchedules() {
|
|
7528
|
-
return Array.from(this.schedules.values());
|
|
7529
|
-
}
|
|
7530
|
-
getEvents() {
|
|
7531
|
-
return this.events;
|
|
7532
|
-
}
|
|
7533
|
-
getProjectDir() {
|
|
7534
|
-
return this.projectDir;
|
|
7535
|
-
}
|
|
7536
|
-
getValidationError(name) {
|
|
7537
|
-
return this.validationErrors.get(name);
|
|
7538
|
-
}
|
|
7539
|
-
getValidationErrors() {
|
|
7540
|
-
return this.validationErrors;
|
|
7541
|
-
}
|
|
7542
|
-
getAgentRegistry() {
|
|
7543
|
-
return this.agentRegistry;
|
|
7544
|
-
}
|
|
7545
|
-
};
|
|
7546
|
-
|
|
7547
|
-
// ../server/dist/services/runtime-config-store.js
|
|
7548
|
-
import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, existsSync as existsSync2, mkdirSync as mkdirSync2 } from "fs";
|
|
7549
|
-
import { dirname as dirname3, join as join3, isAbsolute } from "path";
|
|
7550
|
-
|
|
7551
|
-
// ../server/dist/services/secret-crypto.js
|
|
7552
|
-
import { createCipheriv, createDecipheriv, randomBytes } from "crypto";
|
|
7553
|
-
import { readFileSync, writeFileSync, existsSync, mkdirSync } from "fs";
|
|
7554
|
-
import { dirname as dirname2, join as join2 } from "path";
|
|
7555
|
-
var ALGO = "aes-256-gcm";
|
|
7556
|
-
function deriveKeyFromEnv(env) {
|
|
7557
|
-
const b64 = env.YAMCHART_CONFIG_SECRET;
|
|
7558
|
-
if (!b64)
|
|
7559
|
-
return null;
|
|
7560
|
-
const key = Buffer.from(b64, "base64");
|
|
7561
|
-
if (key.length !== 32) {
|
|
7562
|
-
throw new Error("YAMCHART_CONFIG_SECRET must be a base64-encoded 32-byte key");
|
|
7563
|
-
}
|
|
7564
|
-
return key;
|
|
7565
|
-
}
|
|
7566
|
-
function getConfigKey(dataDir, env, log) {
|
|
7567
|
-
const fromEnv = deriveKeyFromEnv(env);
|
|
7568
|
-
if (fromEnv)
|
|
7569
|
-
return fromEnv;
|
|
7570
|
-
const keyPath = join2(dataDir, ".config-secret");
|
|
7571
|
-
if (existsSync(keyPath)) {
|
|
7572
|
-
const key2 = Buffer.from(readFileSync(keyPath, "utf8").trim(), "base64");
|
|
7573
|
-
if (key2.length !== 32) {
|
|
7574
|
-
throw new Error(`Key file at ${keyPath} does not contain a 32-byte key (got ${key2.length} bytes). Delete it to regenerate.`);
|
|
7575
|
-
}
|
|
7576
|
-
return key2;
|
|
7577
|
-
}
|
|
7578
|
-
mkdirSync(dirname2(keyPath), { recursive: true });
|
|
7579
|
-
const key = randomBytes(32);
|
|
7580
|
-
writeFileSync(keyPath, key.toString("base64"), { mode: 384 });
|
|
7581
|
-
log?.(`No YAMCHART_CONFIG_SECRET set \u2014 generated a machine-local key at ${keyPath}. Set YAMCHART_CONFIG_SECRET to keep secrets readable across deploys/volumes.`);
|
|
7582
|
-
return key;
|
|
7583
|
-
}
|
|
7584
|
-
function encryptSecret(plain, key) {
|
|
7585
|
-
const iv = randomBytes(12);
|
|
7586
|
-
const cipher = createCipheriv(ALGO, key, iv);
|
|
7587
|
-
const ct = Buffer.concat([cipher.update(plain, "utf8"), cipher.final()]);
|
|
7588
|
-
const tag = cipher.getAuthTag();
|
|
7589
|
-
return `${iv.toString("base64")}:${tag.toString("base64")}:${ct.toString("base64")}`;
|
|
7590
|
-
}
|
|
7591
|
-
function decryptSecret(enc, key) {
|
|
7592
|
-
const [ivB64, tagB64, ctB64] = enc.split(":");
|
|
7593
|
-
if (!ivB64 || !tagB64 || !ctB64)
|
|
7594
|
-
throw new Error("Malformed encrypted secret");
|
|
7595
|
-
const decipher = createDecipheriv(ALGO, key, Buffer.from(ivB64, "base64"));
|
|
7596
|
-
decipher.setAuthTag(Buffer.from(tagB64, "base64"));
|
|
7597
|
-
return Buffer.concat([decipher.update(Buffer.from(ctB64, "base64")), decipher.final()]).toString("utf8");
|
|
7598
|
-
}
|
|
7599
|
-
function maskSecret(plain) {
|
|
7600
|
-
if (plain.length <= 4)
|
|
7601
|
-
return "\u2022\u2022\u2022\u2022";
|
|
7602
|
-
return "\u2022\u2022\u2022\u2022" + plain.slice(-4);
|
|
7603
|
-
}
|
|
7604
|
-
|
|
7605
|
-
// ../server/dist/services/runtime-config-store.js
|
|
7606
|
-
var RuntimeConfigError = class extends Error {
|
|
7607
|
-
};
|
|
7608
|
-
var SECRET_LEAF_KEYS = /* @__PURE__ */ new Set(["password", "private_key", "credentials_json", "database_url", "api_key"]);
|
|
7609
|
-
function transformSecretLeaves(obj, fn) {
|
|
7610
|
-
if (Array.isArray(obj))
|
|
7611
|
-
return obj.map((v) => transformSecretLeaves(v, fn));
|
|
7612
|
-
if (obj && typeof obj === "object") {
|
|
7613
|
-
const out = {};
|
|
7614
|
-
for (const [k, v] of Object.entries(obj)) {
|
|
7615
|
-
out[k] = SECRET_LEAF_KEYS.has(k) && typeof v === "string" ? fn(v) : transformSecretLeaves(v, fn);
|
|
7616
|
-
}
|
|
7617
|
-
return out;
|
|
7618
|
-
}
|
|
7619
|
-
return obj;
|
|
7620
|
-
}
|
|
7621
|
-
var RuntimeConfigStore = class {
|
|
7622
|
-
filePath;
|
|
7623
|
-
key;
|
|
7624
|
-
constructor(filePath, key) {
|
|
7625
|
-
this.filePath = filePath;
|
|
7626
|
-
this.key = key;
|
|
7627
|
-
}
|
|
7628
|
-
/** Directory the store (and key file) live in — useful for getConfigKey(). */
|
|
7629
|
-
get dataDir() {
|
|
7630
|
-
return dirname3(this.filePath);
|
|
7631
|
-
}
|
|
7632
|
-
static resolvePath(opts) {
|
|
7633
|
-
if (opts.env.YAMCHART_RUNTIME_CONFIG)
|
|
7634
|
-
return opts.env.YAMCHART_RUNTIME_CONFIG;
|
|
7635
|
-
if (opts.authDbPath && isAbsolute(opts.authDbPath))
|
|
7636
|
-
return join3(dirname3(opts.authDbPath), "runtime-config.json");
|
|
7637
|
-
return join3(opts.projectDir, ".yamchart", "runtime-config.json");
|
|
7638
|
-
}
|
|
7639
|
-
read() {
|
|
7640
|
-
if (!existsSync2(this.filePath))
|
|
7641
|
-
return { version: 1 };
|
|
7642
|
-
let parsed;
|
|
7643
|
-
try {
|
|
7644
|
-
parsed = JSON.parse(readFileSync2(this.filePath, "utf8"));
|
|
7645
|
-
} catch (err) {
|
|
7646
|
-
throw new RuntimeConfigError(`runtime-config.json is not valid JSON: ${err.message}`);
|
|
7647
|
-
}
|
|
7648
|
-
let decrypted;
|
|
7649
|
-
try {
|
|
7650
|
-
decrypted = transformSecretLeaves(parsed, (s) => decryptSecret(s, this.key));
|
|
7651
|
-
} catch (err) {
|
|
7652
|
-
throw new RuntimeConfigError(`Could not decrypt runtime-config.json (wrong YAMCHART_CONFIG_SECRET?): ${err.message}`);
|
|
7653
|
-
}
|
|
7654
|
-
const result = RuntimeConfigSchema.safeParse(decrypted);
|
|
7655
|
-
if (!result.success)
|
|
7656
|
-
throw new RuntimeConfigError(`Invalid runtime-config.json: ${result.error.message}`);
|
|
7657
|
-
return result.data;
|
|
7658
|
-
}
|
|
7659
|
-
write(config) {
|
|
7660
|
-
const validated = RuntimeConfigSchema.parse(config);
|
|
7661
|
-
const encrypted = transformSecretLeaves(validated, (s) => encryptSecret(s, this.key));
|
|
7662
|
-
mkdirSync2(dirname3(this.filePath), { recursive: true, mode: 448 });
|
|
7663
|
-
writeFileSync2(this.filePath, JSON.stringify(encrypted, null, 2), { mode: 384 });
|
|
7664
|
-
}
|
|
7665
|
-
};
|
|
7666
|
-
|
|
7667
|
-
// ../server/dist/services/restart.js
|
|
7668
|
-
function requestRestart(env, exit = (c) => process.exit(c), schedule = (fn, ms) => {
|
|
7669
|
-
setTimeout(fn, ms).unref?.();
|
|
7670
|
-
}) {
|
|
7671
|
-
if (env.YAMCHART_DEV_CHILD === "1") {
|
|
7672
|
-
schedule(() => exit(75), 150);
|
|
7673
|
-
return { applying: true, restartRequired: false };
|
|
7674
|
-
}
|
|
7675
|
-
return { applying: false, restartRequired: true };
|
|
7676
|
-
}
|
|
7677
|
-
|
|
7678
|
-
// ../server/dist/services/connection-refs.js
|
|
7679
|
-
function findConnectionRefs(connectionName, input) {
|
|
7680
|
-
const refs = [];
|
|
7681
|
-
const resolves = (explicit) => (explicit ?? input.defaultConnection) === connectionName;
|
|
7682
|
-
for (const c of input.charts) {
|
|
7683
|
-
if (resolves(c.source?.connection))
|
|
7684
|
-
refs.push({ kind: "chart", name: c.name });
|
|
7685
|
-
}
|
|
7686
|
-
for (const m of input.models) {
|
|
7687
|
-
if (resolves(m.metadata.connection))
|
|
7688
|
-
refs.push({ kind: "model", name: m.metadata.name });
|
|
7689
|
-
}
|
|
7690
|
-
return refs;
|
|
7691
|
-
}
|
|
7692
|
-
|
|
7693
|
-
// ../server/dist/services/cache.js
|
|
7694
|
-
import { LRUCache } from "lru-cache";
|
|
7695
|
-
var MemoryCache = class {
|
|
7696
|
-
cache;
|
|
7697
|
-
defaultTtlMs;
|
|
7698
|
-
hits = 0;
|
|
7699
|
-
misses = 0;
|
|
7700
|
-
constructor(options) {
|
|
7701
|
-
this.defaultTtlMs = options.defaultTtlMs;
|
|
7702
|
-
this.cache = new LRUCache({
|
|
7703
|
-
max: options.maxSize,
|
|
7704
|
-
ttl: options.defaultTtlMs,
|
|
7705
|
-
ttlAutopurge: true
|
|
7706
|
-
});
|
|
7707
|
-
}
|
|
7708
|
-
async get(key) {
|
|
7709
|
-
const value = this.cache.get(key);
|
|
7710
|
-
if (value) {
|
|
7711
|
-
this.hits++;
|
|
7712
|
-
return value;
|
|
7713
|
-
}
|
|
7714
|
-
this.misses++;
|
|
7715
|
-
return null;
|
|
7716
|
-
}
|
|
7717
|
-
async set(key, value, ttlMs) {
|
|
7718
|
-
this.cache.set(key, value, { ttl: ttlMs ?? this.defaultTtlMs });
|
|
7719
|
-
}
|
|
7720
|
-
async invalidate(key) {
|
|
7721
|
-
this.cache.delete(key);
|
|
7722
|
-
}
|
|
7723
|
-
async invalidatePattern(pattern) {
|
|
7724
|
-
const regexPattern = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*");
|
|
7725
|
-
const regex = new RegExp(`^${regexPattern}$`);
|
|
7726
|
-
for (const key of this.cache.keys()) {
|
|
7727
|
-
if (regex.test(key)) {
|
|
7728
|
-
this.cache.delete(key);
|
|
7729
|
-
}
|
|
7730
|
-
}
|
|
7945
|
+
getConnectionSecrets(name) {
|
|
7946
|
+
return this.connectionSecrets.get(name);
|
|
7731
7947
|
}
|
|
7732
|
-
|
|
7733
|
-
|
|
7948
|
+
/** Knowledge DB URL: runtime override wins, else the named env var. */
|
|
7949
|
+
getKnowledgeDbUrl(envVarName, env) {
|
|
7950
|
+
return this.runtimeKnowledgeUrl ?? env[envVarName];
|
|
7734
7951
|
}
|
|
7735
|
-
|
|
7736
|
-
|
|
7737
|
-
|
|
7738
|
-
|
|
7739
|
-
|
|
7740
|
-
};
|
|
7952
|
+
/** Effective AI config: Git project.ai with the runtime ai deep-merged over it. */
|
|
7953
|
+
getAiConfig() {
|
|
7954
|
+
const base2 = this.project?.ai;
|
|
7955
|
+
if (!this.runtimeAi)
|
|
7956
|
+
return base2;
|
|
7957
|
+
return deepMerge(base2 ?? {}, this.runtimeAi);
|
|
7741
7958
|
}
|
|
7742
|
-
|
|
7743
|
-
|
|
7744
|
-
const match = ttl.match(/^(\d+)(ms|s|m|h|d)$/);
|
|
7745
|
-
if (!match) {
|
|
7746
|
-
throw new Error(`Invalid TTL format: ${ttl}`);
|
|
7959
|
+
getCharts() {
|
|
7960
|
+
return Array.from(this.charts.values());
|
|
7747
7961
|
}
|
|
7748
|
-
|
|
7749
|
-
|
|
7750
|
-
switch (unit) {
|
|
7751
|
-
case "ms":
|
|
7752
|
-
return value;
|
|
7753
|
-
case "s":
|
|
7754
|
-
return value * 1e3;
|
|
7755
|
-
case "m":
|
|
7756
|
-
return value * 60 * 1e3;
|
|
7757
|
-
case "h":
|
|
7758
|
-
return value * 60 * 60 * 1e3;
|
|
7759
|
-
case "d":
|
|
7760
|
-
return value * 24 * 60 * 60 * 1e3;
|
|
7761
|
-
default:
|
|
7762
|
-
throw new Error(`Unknown TTL unit: ${unit}`);
|
|
7962
|
+
getChartByName(name) {
|
|
7963
|
+
return this.charts.get(name);
|
|
7763
7964
|
}
|
|
7764
|
-
|
|
7765
|
-
|
|
7766
|
-
// ../server/dist/services/query-service.js
|
|
7767
|
-
function quoteIdentifier(name, dialect) {
|
|
7768
|
-
if (dialect === "mysql" || dialect === "bigquery")
|
|
7769
|
-
return `\`${name.replace(/`/g, "``")}\``;
|
|
7770
|
-
return `"${name.replace(/"/g, '""')}"`;
|
|
7771
|
-
}
|
|
7772
|
-
var QueryService = class {
|
|
7773
|
-
compiler;
|
|
7774
|
-
connector;
|
|
7775
|
-
cache;
|
|
7776
|
-
models;
|
|
7777
|
-
refs;
|
|
7778
|
-
dialect;
|
|
7779
|
-
constructor(config) {
|
|
7780
|
-
this.compiler = new QueryCompiler({
|
|
7781
|
-
models: config.models,
|
|
7782
|
-
refs: config.refs
|
|
7783
|
-
});
|
|
7784
|
-
this.connector = config.connector;
|
|
7785
|
-
this.cache = config.cache;
|
|
7786
|
-
this.models = new Map(Object.entries(config.models));
|
|
7787
|
-
this.refs = config.refs;
|
|
7788
|
-
this.dialect = config.dialect ?? "duckdb";
|
|
7965
|
+
getChartFolder(name) {
|
|
7966
|
+
return this.chartFolders.get(name) ?? "";
|
|
7789
7967
|
}
|
|
7790
|
-
|
|
7791
|
-
|
|
7792
|
-
const cached = await this.cache.get(compiled.cacheKey);
|
|
7793
|
-
if (cached) {
|
|
7794
|
-
return {
|
|
7795
|
-
columns: cached.columns,
|
|
7796
|
-
rows: cached.rows,
|
|
7797
|
-
rowCount: cached.rowCount,
|
|
7798
|
-
durationMs: cached.durationMs,
|
|
7799
|
-
cached: true,
|
|
7800
|
-
cacheKey: compiled.cacheKey,
|
|
7801
|
-
compiledSql: compiled.sql
|
|
7802
|
-
};
|
|
7803
|
-
}
|
|
7804
|
-
const result = await this.connector.execute(compiled.sql);
|
|
7805
|
-
const cacheEntry = {
|
|
7806
|
-
columns: result.columns,
|
|
7807
|
-
rows: result.rows,
|
|
7808
|
-
rowCount: result.rowCount,
|
|
7809
|
-
durationMs: result.durationMs,
|
|
7810
|
-
cachedAt: Date.now()
|
|
7811
|
-
};
|
|
7812
|
-
await this.cache.set(compiled.cacheKey, cacheEntry);
|
|
7813
|
-
return {
|
|
7814
|
-
...result,
|
|
7815
|
-
cached: false,
|
|
7816
|
-
cacheKey: compiled.cacheKey,
|
|
7817
|
-
compiledSql: compiled.sql
|
|
7818
|
-
};
|
|
7968
|
+
getChartFilePath(name) {
|
|
7969
|
+
return this.chartFilePaths.get(name);
|
|
7819
7970
|
}
|
|
7820
|
-
|
|
7821
|
-
|
|
7822
|
-
|
|
7823
|
-
|
|
7824
|
-
|
|
7825
|
-
|
|
7826
|
-
|
|
7827
|
-
const
|
|
7828
|
-
|
|
7829
|
-
|
|
7830
|
-
let orderBy = "";
|
|
7831
|
-
if (options.sortField) {
|
|
7832
|
-
const cleanField = options.sortField.replace(/[^\w\s]/g, "");
|
|
7833
|
-
if (cleanField.length > 0) {
|
|
7834
|
-
orderBy = `ORDER BY ${q(cleanField, d)} ${options.sortDirection === "desc" ? "DESC" : "ASC"}`;
|
|
7835
|
-
}
|
|
7836
|
-
}
|
|
7837
|
-
const offset = options.page * options.pageSize;
|
|
7838
|
-
const dataSql = `SELECT * FROM (${baseSql}) _t ${orderBy} LIMIT ${options.pageSize} OFFSET ${offset}`;
|
|
7839
|
-
const countCacheKey = `${compiled.cacheKey}:count`;
|
|
7840
|
-
let totalCount;
|
|
7841
|
-
const cachedCount = await this.cache.get(countCacheKey);
|
|
7842
|
-
if (cachedCount) {
|
|
7843
|
-
totalCount = cachedCount.rowCount;
|
|
7844
|
-
} else {
|
|
7845
|
-
const countSql = `SELECT COUNT(*) AS _count FROM (${baseSql}) _t`;
|
|
7846
|
-
const countResult = await this.connector.execute(countSql);
|
|
7847
|
-
totalCount = Number(countResult.rows[0]?._count ?? 0);
|
|
7848
|
-
await this.cache.set(countCacheKey, {
|
|
7849
|
-
columns: [],
|
|
7850
|
-
rows: [],
|
|
7851
|
-
rowCount: totalCount,
|
|
7852
|
-
durationMs: countResult.durationMs,
|
|
7853
|
-
cachedAt: Date.now()
|
|
7854
|
-
});
|
|
7971
|
+
getModels() {
|
|
7972
|
+
return Array.from(this.models.values());
|
|
7973
|
+
}
|
|
7974
|
+
getModelByName(name) {
|
|
7975
|
+
return this.models.get(name);
|
|
7976
|
+
}
|
|
7977
|
+
getDefaultConnection() {
|
|
7978
|
+
const envConnection = process.env.YAMCHART_CONNECTION;
|
|
7979
|
+
if (envConnection) {
|
|
7980
|
+
return this.connections.get(envConnection);
|
|
7855
7981
|
}
|
|
7856
|
-
|
|
7857
|
-
if (
|
|
7858
|
-
|
|
7859
|
-
const cachedSummary = await this.cache.get(summaryCacheKey);
|
|
7860
|
-
if (cachedSummary && cachedSummary.rows[0]) {
|
|
7861
|
-
summaryValues = cachedSummary.rows[0];
|
|
7862
|
-
} else {
|
|
7863
|
-
const aggExprs = options.summaryColumns.map(({ field, summary }) => {
|
|
7864
|
-
const col = q(field, d);
|
|
7865
|
-
switch (summary) {
|
|
7866
|
-
case "sum":
|
|
7867
|
-
return `SUM(${col}) AS ${q(`_sum_${field}`, d)}`;
|
|
7868
|
-
case "avg":
|
|
7869
|
-
return `AVG(${col}) AS ${q(`_avg_${field}`, d)}`;
|
|
7870
|
-
case "min":
|
|
7871
|
-
return `MIN(${col}) AS ${q(`_min_${field}`, d)}`;
|
|
7872
|
-
case "max":
|
|
7873
|
-
return `MAX(${col}) AS ${q(`_max_${field}`, d)}`;
|
|
7874
|
-
case "count":
|
|
7875
|
-
return `COUNT(${col}) AS ${q(`_count_${field}`, d)}`;
|
|
7876
|
-
default:
|
|
7877
|
-
return null;
|
|
7878
|
-
}
|
|
7879
|
-
}).filter(Boolean);
|
|
7880
|
-
if (aggExprs.length > 0) {
|
|
7881
|
-
const summarySql = `SELECT ${aggExprs.join(", ")} FROM (${baseSql}) _t`;
|
|
7882
|
-
const summaryResult = await this.connector.execute(summarySql);
|
|
7883
|
-
if (summaryResult.rows[0]) {
|
|
7884
|
-
summaryValues = {};
|
|
7885
|
-
for (const { field, summary } of options.summaryColumns) {
|
|
7886
|
-
const key = `_${summary}_${field}`;
|
|
7887
|
-
summaryValues[`${summary}:${field}`] = Number(summaryResult.rows[0][key] ?? 0);
|
|
7888
|
-
}
|
|
7889
|
-
await this.cache.set(summaryCacheKey, {
|
|
7890
|
-
columns: [],
|
|
7891
|
-
rows: [summaryValues],
|
|
7892
|
-
rowCount: 0,
|
|
7893
|
-
durationMs: summaryResult.durationMs,
|
|
7894
|
-
cachedAt: Date.now()
|
|
7895
|
-
});
|
|
7896
|
-
}
|
|
7897
|
-
}
|
|
7898
|
-
}
|
|
7982
|
+
const defaultName = this.project?.defaults?.connection;
|
|
7983
|
+
if (defaultName) {
|
|
7984
|
+
return this.connections.get(defaultName);
|
|
7899
7985
|
}
|
|
7900
|
-
|
|
7901
|
-
return {
|
|
7902
|
-
...result,
|
|
7903
|
-
cached: false,
|
|
7904
|
-
cacheKey: compiled.cacheKey,
|
|
7905
|
-
compiledSql: dataSql,
|
|
7906
|
-
totalCount,
|
|
7907
|
-
page: options.page,
|
|
7908
|
-
pageSize: options.pageSize,
|
|
7909
|
-
summaryValues
|
|
7910
|
-
};
|
|
7986
|
+
return this.connections.values().next().value;
|
|
7911
7987
|
}
|
|
7912
|
-
|
|
7913
|
-
|
|
7914
|
-
* Returns full QueryResult — caller streams to HTTP response.
|
|
7915
|
-
*/
|
|
7916
|
-
async executeChartForExport(chart, params, maxRows, userContext) {
|
|
7917
|
-
const compiled = this.compiler.compile(chart, params, userContext);
|
|
7918
|
-
const sql = `SELECT * FROM (${compiled.sql}) _t LIMIT ${maxRows}`;
|
|
7919
|
-
const result = await this.connector.execute(sql);
|
|
7920
|
-
return {
|
|
7921
|
-
...result,
|
|
7922
|
-
cached: false,
|
|
7923
|
-
cacheKey: compiled.cacheKey,
|
|
7924
|
-
compiledSql: sql
|
|
7925
|
-
};
|
|
7988
|
+
getDashboards() {
|
|
7989
|
+
return Array.from(this.dashboards.values());
|
|
7926
7990
|
}
|
|
7927
|
-
|
|
7928
|
-
|
|
7929
|
-
* Returns the compiled SQL and cache key.
|
|
7930
|
-
*/
|
|
7931
|
-
compileChart(chart, params, userContext) {
|
|
7932
|
-
return this.compiler.compile(chart, params, userContext);
|
|
7991
|
+
getDashboardByName(name) {
|
|
7992
|
+
return this.dashboards.get(name);
|
|
7933
7993
|
}
|
|
7934
|
-
|
|
7935
|
-
this.
|
|
7994
|
+
getDashboardFolder(name) {
|
|
7995
|
+
return this.dashboardFolders.get(name) ?? "";
|
|
7936
7996
|
}
|
|
7937
|
-
|
|
7938
|
-
this.
|
|
7997
|
+
getDashboardFilePath(name) {
|
|
7998
|
+
return this.dashboardFilePaths.get(name);
|
|
7939
7999
|
}
|
|
7940
|
-
|
|
7941
|
-
|
|
7942
|
-
* Called when config is reloaded.
|
|
7943
|
-
*/
|
|
7944
|
-
updateCompiler(config) {
|
|
7945
|
-
this.compiler = new QueryCompiler({
|
|
7946
|
-
models: config.models,
|
|
7947
|
-
refs: config.refs
|
|
7948
|
-
});
|
|
7949
|
-
this.models = new Map(Object.entries(config.models));
|
|
7950
|
-
this.refs = config.refs;
|
|
8000
|
+
getSchedules() {
|
|
8001
|
+
return Array.from(this.schedules.values());
|
|
7951
8002
|
}
|
|
7952
|
-
|
|
7953
|
-
return this.
|
|
8003
|
+
getEvents() {
|
|
8004
|
+
return this.events;
|
|
7954
8005
|
}
|
|
7955
|
-
|
|
7956
|
-
return this.
|
|
8006
|
+
getProjectDir() {
|
|
8007
|
+
return this.projectDir;
|
|
7957
8008
|
}
|
|
7958
|
-
|
|
7959
|
-
|
|
7960
|
-
if (!model) {
|
|
7961
|
-
throw new Error(`Unknown model: ${modelName}`);
|
|
7962
|
-
}
|
|
7963
|
-
const context = createTemplateContext(params, this.refs, userContext);
|
|
7964
|
-
let sql = renderTemplate(model.sql, context);
|
|
7965
|
-
if (filters && Object.keys(filters).length > 0) {
|
|
7966
|
-
const clauses = Object.entries(filters).map(([field, value]) => `${field} = '${value.replace(/'/g, "''")}'`);
|
|
7967
|
-
sql = `SELECT * FROM (${sql}) _detail WHERE ${clauses.join(" AND ")} LIMIT ${limit2}`;
|
|
7968
|
-
} else {
|
|
7969
|
-
sql = `SELECT * FROM (${sql}) _detail LIMIT ${limit2}`;
|
|
7970
|
-
}
|
|
7971
|
-
return this.connector.execute(sql);
|
|
8009
|
+
getValidationError(name) {
|
|
8010
|
+
return this.validationErrors.get(name);
|
|
7972
8011
|
}
|
|
7973
|
-
|
|
7974
|
-
|
|
7975
|
-
|
|
7976
|
-
|
|
7977
|
-
|
|
7978
|
-
const context = createTemplateContext({}, this.refs);
|
|
7979
|
-
const sql = renderTemplate(model.sql, context);
|
|
7980
|
-
const result = await this.connector.execute(sql);
|
|
7981
|
-
return result.rows.map((row) => ({
|
|
7982
|
-
value: String(row[valueField] ?? ""),
|
|
7983
|
-
label: String(row[labelField] ?? row[valueField] ?? "")
|
|
7984
|
-
}));
|
|
8012
|
+
getValidationErrors() {
|
|
8013
|
+
return this.validationErrors;
|
|
8014
|
+
}
|
|
8015
|
+
getAgentRegistry() {
|
|
8016
|
+
return this.agentRegistry;
|
|
7985
8017
|
}
|
|
7986
8018
|
};
|
|
7987
8019
|
|
|
7988
|
-
// ../server/dist/services/
|
|
7989
|
-
import {
|
|
7990
|
-
|
|
7991
|
-
|
|
7992
|
-
|
|
7993
|
-
|
|
7994
|
-
|
|
7995
|
-
|
|
7996
|
-
|
|
7997
|
-
|
|
7998
|
-
|
|
7999
|
-
|
|
8000
|
-
|
|
8001
|
-
|
|
8002
|
-
host: pgConnection.config.host,
|
|
8003
|
-
port: pgConnection.config.port,
|
|
8004
|
-
database: pgConnection.config.database,
|
|
8005
|
-
schema: pgConnection.config.schema,
|
|
8006
|
-
ssl: pgConnection.config.ssl,
|
|
8007
|
-
user: credentials.user,
|
|
8008
|
-
password: credentials.password,
|
|
8009
|
-
min: pgConnection.pool?.min_connections,
|
|
8010
|
-
max: pgConnection.pool?.max_connections,
|
|
8011
|
-
idleTimeoutMillis: pgConnection.pool?.idle_timeout,
|
|
8012
|
-
statementTimeout: pgConnection.query?.timeout
|
|
8013
|
-
});
|
|
8014
|
-
}
|
|
8015
|
-
case "mysql": {
|
|
8016
|
-
const mysqlConnection = connection;
|
|
8017
|
-
const credentials = resolveMySQLAuth(mysqlConnection, options.secrets);
|
|
8018
|
-
return new MySQLConnector({
|
|
8019
|
-
host: mysqlConnection.config.host,
|
|
8020
|
-
port: mysqlConnection.config.port,
|
|
8021
|
-
database: mysqlConnection.config.database,
|
|
8022
|
-
ssl: mysqlConnection.config.ssl,
|
|
8023
|
-
user: credentials.user,
|
|
8024
|
-
password: credentials.password,
|
|
8025
|
-
min: mysqlConnection.pool?.min_connections,
|
|
8026
|
-
max: mysqlConnection.pool?.max_connections,
|
|
8027
|
-
idleTimeoutMillis: mysqlConnection.pool?.idle_timeout,
|
|
8028
|
-
statementTimeout: mysqlConnection.query?.timeout
|
|
8029
|
-
});
|
|
8030
|
-
}
|
|
8031
|
-
case "sqlite": {
|
|
8032
|
-
const sqliteConnection = connection;
|
|
8033
|
-
const dbPath = sqliteConnection.config.path.startsWith("/") ? sqliteConnection.config.path : sqliteConnection.config.path === ":memory:" ? ":memory:" : join4(projectDir, sqliteConnection.config.path);
|
|
8034
|
-
return new SQLiteConnector({ path: dbPath });
|
|
8035
|
-
}
|
|
8036
|
-
case "snowflake": {
|
|
8037
|
-
const sfConnection = connection;
|
|
8038
|
-
const credentials = resolveSnowflakeAuth(sfConnection, options.secrets);
|
|
8039
|
-
return new SnowflakeConnector({
|
|
8040
|
-
account: sfConnection.config.account,
|
|
8041
|
-
username: credentials.username,
|
|
8042
|
-
password: credentials.password,
|
|
8043
|
-
privateKey: credentials.privateKey,
|
|
8044
|
-
authenticator: credentials.authenticator,
|
|
8045
|
-
clientStoreTemporaryCredential: credentials.clientStoreTemporaryCredential,
|
|
8046
|
-
warehouse: sfConnection.config.warehouse,
|
|
8047
|
-
database: sfConnection.config.database,
|
|
8048
|
-
schema: sfConnection.config.schema,
|
|
8049
|
-
role: sfConnection.config.role,
|
|
8050
|
-
statementTimeout: sfConnection.query?.timeout
|
|
8051
|
-
});
|
|
8052
|
-
}
|
|
8053
|
-
case "bigquery": {
|
|
8054
|
-
const bqConnection = connection;
|
|
8055
|
-
const creds = resolveBigQueryAuth(bqConnection, options.secrets);
|
|
8056
|
-
const keyFilename = creds.keyFilename ? creds.keyFilename.startsWith("/") ? creds.keyFilename : join4(projectDir, creds.keyFilename) : void 0;
|
|
8057
|
-
return new BigQueryConnector({
|
|
8058
|
-
projectId: bqConnection.config.project_id,
|
|
8059
|
-
dataset: bqConnection.config.dataset,
|
|
8060
|
-
location: bqConnection.config.location,
|
|
8061
|
-
maxBytesBilled: bqConnection.config.max_bytes_billed,
|
|
8062
|
-
credentials: creds.credentials,
|
|
8063
|
-
keyFilename
|
|
8064
|
-
});
|
|
8065
|
-
}
|
|
8066
|
-
default:
|
|
8067
|
-
throw new Error(`Unsupported connection type: ${connection.type}`);
|
|
8020
|
+
// ../server/dist/services/runtime-config-store.js
|
|
8021
|
+
import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, existsSync as existsSync2, mkdirSync as mkdirSync2 } from "fs";
|
|
8022
|
+
import { dirname as dirname3, join as join4, isAbsolute } from "path";
|
|
8023
|
+
|
|
8024
|
+
// ../../packages/crypto/dist/index.js
|
|
8025
|
+
import { createCipheriv, createDecipheriv, randomBytes } from "crypto";
|
|
8026
|
+
var ALGO = "aes-256-gcm";
|
|
8027
|
+
function deriveKeyFromEnv(env) {
|
|
8028
|
+
const b64 = env.YAMCHART_CONFIG_SECRET;
|
|
8029
|
+
if (!b64)
|
|
8030
|
+
return null;
|
|
8031
|
+
const key = Buffer.from(b64, "base64");
|
|
8032
|
+
if (key.length !== 32) {
|
|
8033
|
+
throw new Error("YAMCHART_CONFIG_SECRET must be a base64-encoded 32-byte key");
|
|
8068
8034
|
}
|
|
8035
|
+
return key;
|
|
8069
8036
|
}
|
|
8070
|
-
function
|
|
8071
|
-
const
|
|
8072
|
-
|
|
8073
|
-
|
|
8074
|
-
|
|
8075
|
-
|
|
8076
|
-
|
|
8077
|
-
|
|
8078
|
-
|
|
8079
|
-
|
|
8080
|
-
|
|
8081
|
-
|
|
8082
|
-
|
|
8083
|
-
|
|
8084
|
-
|
|
8085
|
-
|
|
8086
|
-
|
|
8087
|
-
|
|
8088
|
-
|
|
8089
|
-
|
|
8090
|
-
|
|
8091
|
-
|
|
8092
|
-
|
|
8093
|
-
|
|
8094
|
-
|
|
8095
|
-
|
|
8096
|
-
|
|
8097
|
-
|
|
8098
|
-
|
|
8099
|
-
|
|
8100
|
-
|
|
8101
|
-
|
|
8102
|
-
|
|
8103
|
-
|
|
8104
|
-
|
|
8105
|
-
|
|
8106
|
-
|
|
8107
|
-
info2.role = c.config.role;
|
|
8108
|
-
break;
|
|
8037
|
+
function encryptSecret(plain, key) {
|
|
8038
|
+
const iv = randomBytes(12);
|
|
8039
|
+
const cipher = createCipheriv(ALGO, key, iv);
|
|
8040
|
+
const ct = Buffer.concat([cipher.update(plain, "utf8"), cipher.final()]);
|
|
8041
|
+
const tag = cipher.getAuthTag();
|
|
8042
|
+
return `${iv.toString("base64")}:${tag.toString("base64")}:${ct.toString("base64")}`;
|
|
8043
|
+
}
|
|
8044
|
+
function decryptSecret(enc, key) {
|
|
8045
|
+
const parts = enc.split(":");
|
|
8046
|
+
if (parts.length !== 3)
|
|
8047
|
+
throw new Error("malformed ciphertext");
|
|
8048
|
+
const ivB64 = parts[0];
|
|
8049
|
+
const tagB64 = parts[1];
|
|
8050
|
+
const ctB64 = parts[2];
|
|
8051
|
+
const decipher = createDecipheriv(ALGO, key, Buffer.from(ivB64, "base64"));
|
|
8052
|
+
decipher.setAuthTag(Buffer.from(tagB64, "base64"));
|
|
8053
|
+
return Buffer.concat([decipher.update(Buffer.from(ctB64, "base64")), decipher.final()]).toString("utf8");
|
|
8054
|
+
}
|
|
8055
|
+
function maskSecret(plain) {
|
|
8056
|
+
if (plain.length <= 4)
|
|
8057
|
+
return "\u2022\u2022\u2022\u2022";
|
|
8058
|
+
return "\u2022\u2022\u2022\u2022" + plain.slice(-4);
|
|
8059
|
+
}
|
|
8060
|
+
|
|
8061
|
+
// ../server/dist/services/secret-crypto.js
|
|
8062
|
+
import { readFileSync, writeFileSync, existsSync, mkdirSync } from "fs";
|
|
8063
|
+
import { dirname as dirname2, join as join3 } from "path";
|
|
8064
|
+
import { randomBytes as randomBytes2 } from "crypto";
|
|
8065
|
+
function getConfigKey(dataDir, env, log) {
|
|
8066
|
+
const fromEnv = deriveKeyFromEnv(env);
|
|
8067
|
+
if (fromEnv)
|
|
8068
|
+
return fromEnv;
|
|
8069
|
+
const keyPath = join3(dataDir, ".config-secret");
|
|
8070
|
+
if (existsSync(keyPath)) {
|
|
8071
|
+
const key2 = Buffer.from(readFileSync(keyPath, "utf8").trim(), "base64");
|
|
8072
|
+
if (key2.length !== 32) {
|
|
8073
|
+
throw new Error(`Key file at ${keyPath} does not contain a 32-byte key (got ${key2.length} bytes). Delete it to regenerate.`);
|
|
8109
8074
|
}
|
|
8110
|
-
|
|
8111
|
-
|
|
8112
|
-
|
|
8113
|
-
|
|
8114
|
-
|
|
8075
|
+
return key2;
|
|
8076
|
+
}
|
|
8077
|
+
mkdirSync(dirname2(keyPath), { recursive: true });
|
|
8078
|
+
const key = randomBytes2(32);
|
|
8079
|
+
writeFileSync(keyPath, key.toString("base64"), { mode: 384 });
|
|
8080
|
+
log?.(`No YAMCHART_CONFIG_SECRET set \u2014 generated a machine-local key at ${keyPath}. Set YAMCHART_CONFIG_SECRET to keep secrets readable across deploys/volumes.`);
|
|
8081
|
+
return key;
|
|
8082
|
+
}
|
|
8083
|
+
|
|
8084
|
+
// ../server/dist/services/runtime-config-store.js
|
|
8085
|
+
var RuntimeConfigError = class extends Error {
|
|
8086
|
+
};
|
|
8087
|
+
var SECRET_LEAF_KEYS = /* @__PURE__ */ new Set(["password", "private_key", "credentials_json", "database_url", "api_key"]);
|
|
8088
|
+
function transformSecretLeaves(obj, fn) {
|
|
8089
|
+
if (Array.isArray(obj))
|
|
8090
|
+
return obj.map((v) => transformSecretLeaves(v, fn));
|
|
8091
|
+
if (obj && typeof obj === "object") {
|
|
8092
|
+
const out = {};
|
|
8093
|
+
for (const [k, v] of Object.entries(obj)) {
|
|
8094
|
+
out[k] = SECRET_LEAF_KEYS.has(k) && typeof v === "string" ? fn(v) : transformSecretLeaves(v, fn);
|
|
8115
8095
|
}
|
|
8096
|
+
return out;
|
|
8116
8097
|
}
|
|
8117
|
-
return
|
|
8098
|
+
return obj;
|
|
8118
8099
|
}
|
|
8119
|
-
|
|
8120
|
-
|
|
8121
|
-
|
|
8122
|
-
|
|
8123
|
-
|
|
8124
|
-
|
|
8125
|
-
|
|
8126
|
-
|
|
8127
|
-
|
|
8128
|
-
return
|
|
8129
|
-
|
|
8130
|
-
|
|
8131
|
-
|
|
8132
|
-
|
|
8133
|
-
|
|
8134
|
-
|
|
8135
|
-
|
|
8136
|
-
|
|
8137
|
-
|
|
8138
|
-
|
|
8139
|
-
|
|
8140
|
-
|
|
8141
|
-
|
|
8100
|
+
var RuntimeConfigStore = class {
|
|
8101
|
+
filePath;
|
|
8102
|
+
key;
|
|
8103
|
+
constructor(filePath, key) {
|
|
8104
|
+
this.filePath = filePath;
|
|
8105
|
+
this.key = key;
|
|
8106
|
+
}
|
|
8107
|
+
/** Directory the store (and key file) live in — useful for getConfigKey(). */
|
|
8108
|
+
get dataDir() {
|
|
8109
|
+
return dirname3(this.filePath);
|
|
8110
|
+
}
|
|
8111
|
+
static resolvePath(opts) {
|
|
8112
|
+
if (opts.env.YAMCHART_RUNTIME_CONFIG)
|
|
8113
|
+
return opts.env.YAMCHART_RUNTIME_CONFIG;
|
|
8114
|
+
if (opts.authDbPath && isAbsolute(opts.authDbPath))
|
|
8115
|
+
return join4(dirname3(opts.authDbPath), "runtime-config.json");
|
|
8116
|
+
return join4(opts.projectDir, ".yamchart", "runtime-config.json");
|
|
8117
|
+
}
|
|
8118
|
+
read() {
|
|
8119
|
+
if (!existsSync2(this.filePath))
|
|
8120
|
+
return { version: 1 };
|
|
8121
|
+
let parsed;
|
|
8122
|
+
try {
|
|
8123
|
+
parsed = JSON.parse(readFileSync2(this.filePath, "utf8"));
|
|
8124
|
+
} catch (err) {
|
|
8125
|
+
throw new RuntimeConfigError(`runtime-config.json is not valid JSON: ${err.message}`);
|
|
8142
8126
|
}
|
|
8143
|
-
|
|
8144
|
-
|
|
8145
|
-
|
|
8146
|
-
|
|
8147
|
-
|
|
8148
|
-
error: errorMessage
|
|
8149
|
-
};
|
|
8150
|
-
} finally {
|
|
8151
|
-
if (connector) {
|
|
8152
|
-
try {
|
|
8153
|
-
await connector.disconnect();
|
|
8154
|
-
} catch {
|
|
8155
|
-
}
|
|
8127
|
+
let decrypted;
|
|
8128
|
+
try {
|
|
8129
|
+
decrypted = transformSecretLeaves(parsed, (s) => decryptSecret(s, this.key));
|
|
8130
|
+
} catch (err) {
|
|
8131
|
+
throw new RuntimeConfigError(`Could not decrypt runtime-config.json (wrong YAMCHART_CONFIG_SECRET?): ${err.message}`);
|
|
8156
8132
|
}
|
|
8133
|
+
const result = RuntimeConfigSchema.safeParse(decrypted);
|
|
8134
|
+
if (!result.success)
|
|
8135
|
+
throw new RuntimeConfigError(`Invalid runtime-config.json: ${result.error.message}`);
|
|
8136
|
+
return result.data;
|
|
8137
|
+
}
|
|
8138
|
+
write(config) {
|
|
8139
|
+
const validated = RuntimeConfigSchema.parse(config);
|
|
8140
|
+
const encrypted = transformSecretLeaves(validated, (s) => encryptSecret(s, this.key));
|
|
8141
|
+
mkdirSync2(dirname3(this.filePath), { recursive: true, mode: 448 });
|
|
8142
|
+
writeFileSync2(this.filePath, JSON.stringify(encrypted, null, 2), { mode: 384 });
|
|
8143
|
+
}
|
|
8144
|
+
};
|
|
8145
|
+
|
|
8146
|
+
// ../server/dist/services/restart.js
|
|
8147
|
+
function requestRestart(env, exit = (c) => process.exit(c), schedule = (fn, ms) => {
|
|
8148
|
+
setTimeout(fn, ms).unref?.();
|
|
8149
|
+
}) {
|
|
8150
|
+
if (env.YAMCHART_DEV_CHILD === "1") {
|
|
8151
|
+
schedule(() => exit(75), 150);
|
|
8152
|
+
return { applying: true, restartRequired: false };
|
|
8153
|
+
}
|
|
8154
|
+
return { applying: false, restartRequired: true };
|
|
8155
|
+
}
|
|
8156
|
+
|
|
8157
|
+
// ../server/dist/services/connection-refs.js
|
|
8158
|
+
function findConnectionRefs(connectionName, input) {
|
|
8159
|
+
const refs = [];
|
|
8160
|
+
const resolves = (explicit) => (explicit ?? input.defaultConnection) === connectionName;
|
|
8161
|
+
for (const c of input.charts) {
|
|
8162
|
+
if (resolves(c.source?.connection))
|
|
8163
|
+
refs.push({ kind: "chart", name: c.name });
|
|
8164
|
+
}
|
|
8165
|
+
for (const m of input.models) {
|
|
8166
|
+
if (resolves(m.metadata.connection))
|
|
8167
|
+
refs.push({ kind: "model", name: m.metadata.name });
|
|
8157
8168
|
}
|
|
8169
|
+
return refs;
|
|
8158
8170
|
}
|
|
8159
8171
|
|
|
8160
8172
|
// ../server/dist/routes/config.js
|
|
@@ -9019,8 +9031,8 @@ async function semanticRoutes(fastify, options) {
|
|
|
9019
9031
|
});
|
|
9020
9032
|
}
|
|
9021
9033
|
const { writeFileSync: writeFileSync3, mkdirSync: mkdirSync3 } = await import("fs");
|
|
9022
|
-
const { join:
|
|
9023
|
-
const fullPath =
|
|
9034
|
+
const { join: join15, dirname: dirname5 } = await import("path");
|
|
9035
|
+
const fullPath = join15(projectDir, filePath);
|
|
9024
9036
|
mkdirSync3(dirname5(fullPath), { recursive: true });
|
|
9025
9037
|
writeFileSync3(fullPath, sql, "utf-8");
|
|
9026
9038
|
return { saved: true, path: filePath };
|
|
@@ -9133,7 +9145,7 @@ var SlackNotifier = class {
|
|
|
9133
9145
|
let response = await fetch(webhookUrl, options);
|
|
9134
9146
|
if (!response.ok) {
|
|
9135
9147
|
if (this.retryDelayMs > 0) {
|
|
9136
|
-
await new Promise((
|
|
9148
|
+
await new Promise((resolve4) => setTimeout(resolve4, this.retryDelayMs));
|
|
9137
9149
|
}
|
|
9138
9150
|
response = await fetch(webhookUrl, options);
|
|
9139
9151
|
if (!response.ok) {
|
|
@@ -9449,6 +9461,8 @@ function rowToBlindspot(r) {
|
|
|
9449
9461
|
confidence: r.confidence ?? void 0,
|
|
9450
9462
|
status: r.status,
|
|
9451
9463
|
answer: r.answer ?? void 0,
|
|
9464
|
+
prUrl: r.pr_url ?? void 0,
|
|
9465
|
+
resolvedByKnowledge: r.resolved_by_knowledge ?? false,
|
|
9452
9466
|
createdAt: new Date(r.created_at).toISOString(),
|
|
9453
9467
|
updatedAt: new Date(r.updated_at).toISOString()
|
|
9454
9468
|
};
|
|
@@ -9476,6 +9490,7 @@ function rowToHunch(r) {
|
|
|
9476
9490
|
rationale: r.rationale ?? void 0,
|
|
9477
9491
|
confidence: r.confidence ?? void 0,
|
|
9478
9492
|
lastRunSample: r.last_run_sample ?? void 0,
|
|
9493
|
+
autoConfirmed: r.auto_confirmed ?? false,
|
|
9479
9494
|
createdAt: new Date(r.created_at).toISOString(),
|
|
9480
9495
|
updatedAt: new Date(r.updated_at).toISOString()
|
|
9481
9496
|
};
|
|
@@ -9595,6 +9610,14 @@ var PostgresKnowledgeStore = class {
|
|
|
9595
9610
|
params.push(patch.answer);
|
|
9596
9611
|
sets.push(`answer = $${params.length}`);
|
|
9597
9612
|
}
|
|
9613
|
+
if (patch.resolvedByKnowledge !== void 0) {
|
|
9614
|
+
params.push(patch.resolvedByKnowledge);
|
|
9615
|
+
sets.push(`resolved_by_knowledge = $${params.length}`);
|
|
9616
|
+
}
|
|
9617
|
+
if (patch.prUrl !== void 0) {
|
|
9618
|
+
params.push(patch.prUrl);
|
|
9619
|
+
sets.push(`pr_url = $${params.length}`);
|
|
9620
|
+
}
|
|
9598
9621
|
params.push((/* @__PURE__ */ new Date()).toISOString());
|
|
9599
9622
|
sets.push(`updated_at = $${params.length}`);
|
|
9600
9623
|
params.push(id);
|
|
@@ -9701,8 +9724,8 @@ var PostgresKnowledgeStore = class {
|
|
|
9701
9724
|
]);
|
|
9702
9725
|
}
|
|
9703
9726
|
for (const b of snapshot.blindspots) {
|
|
9704
|
-
await this.pool.query(`INSERT INTO blindspots (id, connection, kind, title, detail, subject, suggested_model, confidence, status, answer, created_at, updated_at)
|
|
9705
|
-
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12) ON CONFLICT (id) DO NOTHING`, [
|
|
9727
|
+
await this.pool.query(`INSERT INTO blindspots (id, connection, kind, title, detail, subject, suggested_model, confidence, status, answer, resolved_by_knowledge, pr_url, created_at, updated_at)
|
|
9728
|
+
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14) ON CONFLICT (id) DO NOTHING`, [
|
|
9706
9729
|
b.id,
|
|
9707
9730
|
b.connection,
|
|
9708
9731
|
b.kind,
|
|
@@ -9713,6 +9736,8 @@ var PostgresKnowledgeStore = class {
|
|
|
9713
9736
|
b.confidence ?? null,
|
|
9714
9737
|
b.status,
|
|
9715
9738
|
b.answer ?? null,
|
|
9739
|
+
b.resolvedByKnowledge ?? false,
|
|
9740
|
+
b.prUrl ?? null,
|
|
9716
9741
|
b.createdAt,
|
|
9717
9742
|
b.updatedAt
|
|
9718
9743
|
]);
|
|
@@ -9743,11 +9768,11 @@ function __rest(s, e) {
|
|
|
9743
9768
|
}
|
|
9744
9769
|
function __awaiter(thisArg, _arguments, P, generator) {
|
|
9745
9770
|
function adopt(value) {
|
|
9746
|
-
return value instanceof P ? value : new P(function(
|
|
9747
|
-
|
|
9771
|
+
return value instanceof P ? value : new P(function(resolve4) {
|
|
9772
|
+
resolve4(value);
|
|
9748
9773
|
});
|
|
9749
9774
|
}
|
|
9750
|
-
return new (P || (P = Promise))(function(
|
|
9775
|
+
return new (P || (P = Promise))(function(resolve4, reject) {
|
|
9751
9776
|
function fulfilled(value) {
|
|
9752
9777
|
try {
|
|
9753
9778
|
step(generator.next(value));
|
|
@@ -9763,7 +9788,7 @@ function __awaiter(thisArg, _arguments, P, generator) {
|
|
|
9763
9788
|
}
|
|
9764
9789
|
}
|
|
9765
9790
|
function step(result) {
|
|
9766
|
-
result.done ?
|
|
9791
|
+
result.done ? resolve4(result.value) : adopt(result.value).then(fulfilled, rejected);
|
|
9767
9792
|
}
|
|
9768
9793
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9769
9794
|
});
|
|
@@ -12294,15 +12319,15 @@ var RealtimeChannel = class _RealtimeChannel {
|
|
|
12294
12319
|
}
|
|
12295
12320
|
}
|
|
12296
12321
|
} else {
|
|
12297
|
-
return new Promise((
|
|
12322
|
+
return new Promise((resolve4) => {
|
|
12298
12323
|
var _a2, _b2, _c;
|
|
12299
12324
|
const push = this._push(args.type, args, opts.timeout || this.timeout);
|
|
12300
12325
|
if (args.type === "broadcast" && !((_c = (_b2 = (_a2 = this.params) === null || _a2 === void 0 ? void 0 : _a2.config) === null || _b2 === void 0 ? void 0 : _b2.broadcast) === null || _c === void 0 ? void 0 : _c.ack)) {
|
|
12301
|
-
|
|
12326
|
+
resolve4("ok");
|
|
12302
12327
|
}
|
|
12303
|
-
push.receive("ok", () =>
|
|
12304
|
-
push.receive("error", () =>
|
|
12305
|
-
push.receive("timeout", () =>
|
|
12328
|
+
push.receive("ok", () => resolve4("ok"));
|
|
12329
|
+
push.receive("error", () => resolve4("error"));
|
|
12330
|
+
push.receive("timeout", () => resolve4("timed out"));
|
|
12306
12331
|
});
|
|
12307
12332
|
}
|
|
12308
12333
|
}
|
|
@@ -12330,16 +12355,16 @@ var RealtimeChannel = class _RealtimeChannel {
|
|
|
12330
12355
|
};
|
|
12331
12356
|
this.joinPush.destroy();
|
|
12332
12357
|
let leavePush = null;
|
|
12333
|
-
return new Promise((
|
|
12358
|
+
return new Promise((resolve4) => {
|
|
12334
12359
|
leavePush = new Push(this, CHANNEL_EVENTS.leave, {}, timeout);
|
|
12335
12360
|
leavePush.receive("ok", () => {
|
|
12336
12361
|
onClose();
|
|
12337
|
-
|
|
12362
|
+
resolve4("ok");
|
|
12338
12363
|
}).receive("timeout", () => {
|
|
12339
12364
|
onClose();
|
|
12340
|
-
|
|
12365
|
+
resolve4("timed out");
|
|
12341
12366
|
}).receive("error", () => {
|
|
12342
|
-
|
|
12367
|
+
resolve4("error");
|
|
12343
12368
|
});
|
|
12344
12369
|
leavePush.send();
|
|
12345
12370
|
if (!this._canPush()) {
|
|
@@ -12418,8 +12443,8 @@ var RealtimeChannel = class _RealtimeChannel {
|
|
|
12418
12443
|
_trigger(type, payload, ref) {
|
|
12419
12444
|
var _a, _b;
|
|
12420
12445
|
const typeLower = type.toLocaleLowerCase();
|
|
12421
|
-
const { close, error: error2, leave, join:
|
|
12422
|
-
const events = [close, error2, leave,
|
|
12446
|
+
const { close, error: error2, leave, join: join15 } = CHANNEL_EVENTS;
|
|
12447
|
+
const events = [close, error2, leave, join15];
|
|
12423
12448
|
if (ref && events.indexOf(typeLower) >= 0 && ref !== this._joinRef()) {
|
|
12424
12449
|
return;
|
|
12425
12450
|
}
|
|
@@ -14035,7 +14060,7 @@ var _getRequestParams = (method, options, parameters, body) => {
|
|
|
14035
14060
|
return _objectSpread2(_objectSpread2({}, params), parameters);
|
|
14036
14061
|
};
|
|
14037
14062
|
async function _handleRequest(fetcher, method, url, options, parameters, body, namespace) {
|
|
14038
|
-
return new Promise((
|
|
14063
|
+
return new Promise((resolve4, reject) => {
|
|
14039
14064
|
fetcher(url, _getRequestParams(method, options, parameters, body)).then((result) => {
|
|
14040
14065
|
if (!result.ok) throw result;
|
|
14041
14066
|
if (options === null || options === void 0 ? void 0 : options.noResolveJson) return result;
|
|
@@ -14045,7 +14070,7 @@ async function _handleRequest(fetcher, method, url, options, parameters, body, n
|
|
|
14045
14070
|
if (!contentType || !contentType.includes("application/json")) return {};
|
|
14046
14071
|
}
|
|
14047
14072
|
return result.json();
|
|
14048
|
-
}).then((data) =>
|
|
14073
|
+
}).then((data) => resolve4(data)).catch((error2) => handleError(error2, reject, options, namespace));
|
|
14049
14074
|
});
|
|
14050
14075
|
}
|
|
14051
14076
|
function createFetchApi(namespace = "storage") {
|
|
@@ -22642,7 +22667,7 @@ var responseViaResponseObject = async (res, outgoing, options = {}) => {
|
|
|
22642
22667
|
});
|
|
22643
22668
|
if (!chunk) {
|
|
22644
22669
|
if (i === 1) {
|
|
22645
|
-
await new Promise((
|
|
22670
|
+
await new Promise((resolve4) => setTimeout(resolve4));
|
|
22646
22671
|
maxReadCount = 3;
|
|
22647
22672
|
continue;
|
|
22648
22673
|
}
|
|
@@ -24676,9 +24701,9 @@ data:
|
|
|
24676
24701
|
const initRequest = messages.find((m) => isInitializeRequest(m));
|
|
24677
24702
|
const clientProtocolVersion = initRequest ? initRequest.params.protocolVersion : req.headers.get("mcp-protocol-version") ?? DEFAULT_NEGOTIATED_PROTOCOL_VERSION;
|
|
24678
24703
|
if (this._enableJsonResponse) {
|
|
24679
|
-
return new Promise((
|
|
24704
|
+
return new Promise((resolve4) => {
|
|
24680
24705
|
this._streamMapping.set(streamId, {
|
|
24681
|
-
resolveJson:
|
|
24706
|
+
resolveJson: resolve4,
|
|
24682
24707
|
cleanup: () => {
|
|
24683
24708
|
this._streamMapping.delete(streamId);
|
|
24684
24709
|
}
|
|
@@ -26995,7 +27020,7 @@ var Protocol = class {
|
|
|
26995
27020
|
return;
|
|
26996
27021
|
}
|
|
26997
27022
|
const pollInterval = task2.pollInterval ?? this._options?.defaultTaskPollInterval ?? 1e3;
|
|
26998
|
-
await new Promise((
|
|
27023
|
+
await new Promise((resolve4) => setTimeout(resolve4, pollInterval));
|
|
26999
27024
|
options?.signal?.throwIfAborted();
|
|
27000
27025
|
}
|
|
27001
27026
|
} catch (error2) {
|
|
@@ -27012,7 +27037,7 @@ var Protocol = class {
|
|
|
27012
27037
|
*/
|
|
27013
27038
|
request(request, resultSchema, options) {
|
|
27014
27039
|
const { relatedRequestId, resumptionToken, onresumptiontoken, task, relatedTask } = options ?? {};
|
|
27015
|
-
return new Promise((
|
|
27040
|
+
return new Promise((resolve4, reject) => {
|
|
27016
27041
|
const earlyReject = (error2) => {
|
|
27017
27042
|
reject(error2);
|
|
27018
27043
|
};
|
|
@@ -27090,7 +27115,7 @@ var Protocol = class {
|
|
|
27090
27115
|
if (!parseResult.success) {
|
|
27091
27116
|
reject(parseResult.error);
|
|
27092
27117
|
} else {
|
|
27093
|
-
|
|
27118
|
+
resolve4(parseResult.data);
|
|
27094
27119
|
}
|
|
27095
27120
|
} catch (error2) {
|
|
27096
27121
|
reject(error2);
|
|
@@ -27351,12 +27376,12 @@ var Protocol = class {
|
|
|
27351
27376
|
}
|
|
27352
27377
|
} catch {
|
|
27353
27378
|
}
|
|
27354
|
-
return new Promise((
|
|
27379
|
+
return new Promise((resolve4, reject) => {
|
|
27355
27380
|
if (signal.aborted) {
|
|
27356
27381
|
reject(new McpError(ErrorCode.InvalidRequest, "Request cancelled"));
|
|
27357
27382
|
return;
|
|
27358
27383
|
}
|
|
27359
|
-
const timeoutId = setTimeout(
|
|
27384
|
+
const timeoutId = setTimeout(resolve4, interval);
|
|
27360
27385
|
signal.addEventListener("abort", () => {
|
|
27361
27386
|
clearTimeout(timeoutId);
|
|
27362
27387
|
reject(new McpError(ErrorCode.InvalidRequest, "Request cancelled"));
|
|
@@ -28457,7 +28482,7 @@ var McpServer = class {
|
|
|
28457
28482
|
let task = createTaskResult.task;
|
|
28458
28483
|
const pollInterval = task.pollInterval ?? 5e3;
|
|
28459
28484
|
while (task.status !== "completed" && task.status !== "failed" && task.status !== "cancelled") {
|
|
28460
|
-
await new Promise((
|
|
28485
|
+
await new Promise((resolve4) => setTimeout(resolve4, pollInterval));
|
|
28461
28486
|
const updatedTask = await extra.taskStore.getTask(taskId);
|
|
28462
28487
|
if (!updatedTask) {
|
|
28463
28488
|
throw new McpError(ErrorCode.InternalError, `Task ${taskId} not found during polling`);
|
|
@@ -29032,13 +29057,20 @@ import { z as z2 } from "zod";
|
|
|
29032
29057
|
async function listCharts(deps) {
|
|
29033
29058
|
const charts = deps.configLoader.getCharts().map((c) => {
|
|
29034
29059
|
const chart = c;
|
|
29060
|
+
const model = chart.source?.model ? deps.configLoader.getModelByName(chart.source.model) : void 0;
|
|
29061
|
+
const modelParameters = model?.metadata?.params ?? [];
|
|
29062
|
+
const parameterDetails = [
|
|
29063
|
+
...modelParameters.map((p) => ({ ...p, source: "model" })),
|
|
29064
|
+
...(chart.parameters ?? []).map((p) => ({ ...p, source: "chart" }))
|
|
29065
|
+
];
|
|
29035
29066
|
return {
|
|
29036
29067
|
name: chart.name,
|
|
29037
29068
|
title: chart.title,
|
|
29038
29069
|
...chart.description ? { description: chart.description } : {},
|
|
29039
29070
|
type: chart.chart?.type,
|
|
29040
29071
|
model: chart.source?.model,
|
|
29041
|
-
parameters: (chart.parameters ?? []).map((p) => p.name)
|
|
29072
|
+
parameters: (chart.parameters ?? []).map((p) => p.name),
|
|
29073
|
+
parameter_details: parameterDetails
|
|
29042
29074
|
};
|
|
29043
29075
|
});
|
|
29044
29076
|
return JSON.stringify(charts);
|
|
@@ -29083,10 +29115,91 @@ async function runSql(deps, args) {
|
|
|
29083
29115
|
async function runModel(deps, args) {
|
|
29084
29116
|
const chart = deps.configLoader.getChartByName(args.name);
|
|
29085
29117
|
if (!chart) {
|
|
29118
|
+
const model = deps.configLoader.getModelByName(args.name);
|
|
29119
|
+
if (model) {
|
|
29120
|
+
const chartsForModel = deps.configLoader.getCharts().filter((c) => c.source?.model === args.name).map((c) => c.name);
|
|
29121
|
+
return [
|
|
29122
|
+
`Error: "${args.name}" is a model, but run_model currently executes saved charts by chart name.`,
|
|
29123
|
+
chartsForModel.length ? `Use one of these chart names for that model: ${chartsForModel.join(", ")}.` : "Create a chart for this model or use run_sql to query the model SQL directly."
|
|
29124
|
+
].join(" ");
|
|
29125
|
+
}
|
|
29086
29126
|
return `Error: Chart "${args.name}" not found. Use list_charts to see available charts.`;
|
|
29087
29127
|
}
|
|
29088
|
-
const
|
|
29089
|
-
|
|
29128
|
+
const normalized = normalizeChartParams(deps, chart, args.params ?? {});
|
|
29129
|
+
if (normalized.error)
|
|
29130
|
+
return `Error: ${JSON.stringify(normalized.error)}`;
|
|
29131
|
+
try {
|
|
29132
|
+
const result = await deps.queryService.executeChart(chart, normalized.params, buildUserContext(deps.user));
|
|
29133
|
+
return JSON.stringify({ columns: result.columns, rows: result.rows });
|
|
29134
|
+
} catch (err) {
|
|
29135
|
+
return `Error: ${err instanceof Error ? err.message : String(err)}`;
|
|
29136
|
+
}
|
|
29137
|
+
}
|
|
29138
|
+
function normalizeChartParams(deps, chart, input) {
|
|
29139
|
+
const definitions = /* @__PURE__ */ new Map();
|
|
29140
|
+
const modelName = chart.source?.model;
|
|
29141
|
+
const model = modelName ? deps.configLoader.getModelByName(modelName) : void 0;
|
|
29142
|
+
for (const param of model?.metadata?.params ?? []) {
|
|
29143
|
+
definitions.set(param.name, param);
|
|
29144
|
+
}
|
|
29145
|
+
for (const param of chart.parameters ?? []) {
|
|
29146
|
+
definitions.set(param.name, param);
|
|
29147
|
+
}
|
|
29148
|
+
const params = { ...input };
|
|
29149
|
+
for (const [name, value] of Object.entries(input)) {
|
|
29150
|
+
const definition = definitions.get(name);
|
|
29151
|
+
if (!definition)
|
|
29152
|
+
continue;
|
|
29153
|
+
const type = definition.type;
|
|
29154
|
+
if (type === "multi_select" || type === "string[]") {
|
|
29155
|
+
if (isAllValue(value)) {
|
|
29156
|
+
params[name] = [];
|
|
29157
|
+
} else if (!Array.isArray(value)) {
|
|
29158
|
+
params[name] = [String(value)];
|
|
29159
|
+
} else {
|
|
29160
|
+
params[name] = value.map((v) => String(v));
|
|
29161
|
+
}
|
|
29162
|
+
continue;
|
|
29163
|
+
}
|
|
29164
|
+
if (type === "number[]") {
|
|
29165
|
+
const values = Array.isArray(value) ? value : [value];
|
|
29166
|
+
const nums = values.map((v) => Number(v));
|
|
29167
|
+
if (nums.some((v) => !Number.isFinite(v))) {
|
|
29168
|
+
return {
|
|
29169
|
+
params,
|
|
29170
|
+
error: {
|
|
29171
|
+
error_type: "invalid_parameter",
|
|
29172
|
+
parameter: name,
|
|
29173
|
+
expected: "array<number>",
|
|
29174
|
+
received: Array.isArray(value) ? "array" : typeof value
|
|
29175
|
+
}
|
|
29176
|
+
};
|
|
29177
|
+
}
|
|
29178
|
+
params[name] = nums;
|
|
29179
|
+
continue;
|
|
29180
|
+
}
|
|
29181
|
+
if (type === "number" && typeof value === "string") {
|
|
29182
|
+
const num2 = Number(value);
|
|
29183
|
+
if (!Number.isFinite(num2)) {
|
|
29184
|
+
return {
|
|
29185
|
+
params,
|
|
29186
|
+
error: {
|
|
29187
|
+
error_type: "invalid_parameter",
|
|
29188
|
+
parameter: name,
|
|
29189
|
+
expected: "number",
|
|
29190
|
+
received: "string"
|
|
29191
|
+
}
|
|
29192
|
+
};
|
|
29193
|
+
}
|
|
29194
|
+
params[name] = num2;
|
|
29195
|
+
}
|
|
29196
|
+
}
|
|
29197
|
+
return { params };
|
|
29198
|
+
}
|
|
29199
|
+
function isAllValue(value) {
|
|
29200
|
+
if (typeof value === "string")
|
|
29201
|
+
return value.trim().toLowerCase() === "all";
|
|
29202
|
+
return Array.isArray(value) && value.length === 1 && isAllValue(value[0]);
|
|
29090
29203
|
}
|
|
29091
29204
|
|
|
29092
29205
|
// ../../packages/mcp-core/dist/catalog.js
|
|
@@ -29124,22 +29237,38 @@ function includeFilter(deps) {
|
|
|
29124
29237
|
}
|
|
29125
29238
|
async function listTables(deps) {
|
|
29126
29239
|
const tables = await readCatalogTables(deps.projectDir, includeFilter(deps));
|
|
29127
|
-
|
|
29128
|
-
|
|
29129
|
-
|
|
29130
|
-
|
|
29131
|
-
|
|
29132
|
-
|
|
29240
|
+
if (tables.length === 0) {
|
|
29241
|
+
const live = await discoverLiveTables(deps);
|
|
29242
|
+
return JSON.stringify({
|
|
29243
|
+
tables: live.tables.map(tableSummary),
|
|
29244
|
+
...live.warning ? { warning: live.warning } : {}
|
|
29245
|
+
});
|
|
29246
|
+
}
|
|
29247
|
+
return JSON.stringify({ tables: tables.map(tableSummary) });
|
|
29133
29248
|
}
|
|
29134
29249
|
async function describeTable(deps, args) {
|
|
29135
|
-
const
|
|
29136
|
-
|
|
29250
|
+
const identifier = parseTableIdentifier(args.table, args.schema);
|
|
29251
|
+
if (!identifier.valid)
|
|
29252
|
+
return `Error: ${identifier.error}`;
|
|
29253
|
+
const normalizedTable = identifier.normalized;
|
|
29254
|
+
const parts = identifier.parts;
|
|
29137
29255
|
const tableName = parts[parts.length - 1] ?? normalizedTable;
|
|
29138
29256
|
const schema = parts.length > 1 ? parts.slice(0, -1).join(".") : void 0;
|
|
29139
29257
|
const tables = await readCatalogTables(deps.projectDir, includeFilter(deps));
|
|
29140
|
-
const match = tables.find((t) => t.name === tableName && (schema === void 0 || t.schema
|
|
29258
|
+
const match = tables.find((t) => t.name === tableName && (schema === void 0 || schemaMatches(t.schema, schema)));
|
|
29141
29259
|
if (!match) {
|
|
29142
|
-
|
|
29260
|
+
try {
|
|
29261
|
+
const result = await deps.queryService.executeRawSql(`SELECT * FROM ${identifier.sql} LIMIT 0`);
|
|
29262
|
+
return JSON.stringify({
|
|
29263
|
+
name: tableName,
|
|
29264
|
+
...schema ? { schema } : {},
|
|
29265
|
+
columns: result.columns,
|
|
29266
|
+
warning: "Table was not found in the cached catalog; columns were discovered with a live LIMIT 0 query."
|
|
29267
|
+
});
|
|
29268
|
+
} catch (err) {
|
|
29269
|
+
const detail2 = err instanceof Error ? err.message : String(err);
|
|
29270
|
+
return `Error: Table "${args.table}" not found in catalog, and live discovery failed: ${detail2}`;
|
|
29271
|
+
}
|
|
29143
29272
|
}
|
|
29144
29273
|
return JSON.stringify({
|
|
29145
29274
|
name: match.name,
|
|
@@ -29147,24 +29276,111 @@ async function describeTable(deps, args) {
|
|
|
29147
29276
|
columns: match.columns
|
|
29148
29277
|
});
|
|
29149
29278
|
}
|
|
29150
|
-
var TABLE_IDENT = /^[a-zA-Z_][a-zA-Z0-9_]*(\.[a-zA-Z_][a-zA-Z0-9_]*)*$/;
|
|
29151
29279
|
async function sampleTable(deps, args) {
|
|
29152
|
-
|
|
29153
|
-
|
|
29154
|
-
|
|
29280
|
+
const identifier = parseTableIdentifier(args.table, args.schema);
|
|
29281
|
+
if (!identifier.valid)
|
|
29282
|
+
return `Error: ${identifier.error}`;
|
|
29155
29283
|
const limit2 = Math.min(Math.max(args.limit ?? 5, 1), 100);
|
|
29156
|
-
const parts =
|
|
29284
|
+
const parts = identifier.parts;
|
|
29157
29285
|
const tableName = parts[parts.length - 1] ?? args.table;
|
|
29158
29286
|
const providedSchema = parts.length > 1 ? parts.slice(0, -1).join(".") : void 0;
|
|
29159
29287
|
const tables = await readCatalogTables(deps.projectDir, includeFilter(deps));
|
|
29160
|
-
const match = tables.find((t) => t.name === tableName && (providedSchema === void 0 || t.schema
|
|
29161
|
-
const from = providedSchema === void 0 && match?.schema ? `${match.schema}.${args.table}` :
|
|
29288
|
+
const match = tables.find((t) => t.name === tableName && (providedSchema === void 0 || schemaMatches(t.schema, providedSchema)));
|
|
29289
|
+
const from = providedSchema === void 0 && match?.schema ? sqlIdentifier(`${match.schema}.${args.table}`) : identifier.sql;
|
|
29162
29290
|
const result = await deps.queryService.executeRawSql(`SELECT * FROM ${from} LIMIT ${limit2}`);
|
|
29163
29291
|
return JSON.stringify({ columns: result.columns, rows: result.rows });
|
|
29164
29292
|
}
|
|
29293
|
+
function tableSummary(t) {
|
|
29294
|
+
return {
|
|
29295
|
+
name: t.name,
|
|
29296
|
+
...t.schema ? { schema: t.schema } : {},
|
|
29297
|
+
connection: t.connection,
|
|
29298
|
+
columnCount: t.columns.length
|
|
29299
|
+
};
|
|
29300
|
+
}
|
|
29301
|
+
function schemaMatches(catalogSchema, requestedSchema) {
|
|
29302
|
+
if (catalogSchema === requestedSchema)
|
|
29303
|
+
return true;
|
|
29304
|
+
return catalogSchema?.endsWith(`.${requestedSchema}`) ?? false;
|
|
29305
|
+
}
|
|
29306
|
+
function parseTableIdentifier(table, schema) {
|
|
29307
|
+
const normalizedInput = table.trim().replace(/^`(.+)`$/, "$1");
|
|
29308
|
+
const normalized = schema != null && !normalizedInput.includes(".") ? `${schema}.${normalizedInput}` : normalizedInput;
|
|
29309
|
+
const parts = normalized.split(".");
|
|
29310
|
+
if (parts.length === 0 || parts.length > 3 || parts.some((p) => !/^[A-Za-z_][A-Za-z0-9_-]*$/.test(p))) {
|
|
29311
|
+
return {
|
|
29312
|
+
valid: false,
|
|
29313
|
+
error: `Invalid table identifier "${table}". Use table, schema.table, project.schema.table, or a backticked fully qualified name.`
|
|
29314
|
+
};
|
|
29315
|
+
}
|
|
29316
|
+
return { valid: true, normalized, parts, sql: sqlIdentifier(normalized) };
|
|
29317
|
+
}
|
|
29318
|
+
function sqlIdentifier(identifier) {
|
|
29319
|
+
const needsQuoting = identifier.split(".").some((p) => p.includes("-"));
|
|
29320
|
+
return needsQuoting ? `\`${identifier}\`` : identifier;
|
|
29321
|
+
}
|
|
29322
|
+
async function discoverLiveTables(deps) {
|
|
29323
|
+
const include = includeFilter(deps);
|
|
29324
|
+
const connectionType = deps.connectionType?.toLowerCase();
|
|
29325
|
+
try {
|
|
29326
|
+
if (connectionType === "bigquery") {
|
|
29327
|
+
const connection2 = deps.configLoader.getDefaultConnection?.();
|
|
29328
|
+
const config = connection2?.type === "bigquery" ? connection2.config : void 0;
|
|
29329
|
+
const projectId = config?.project_id;
|
|
29330
|
+
if (!projectId) {
|
|
29331
|
+
return {
|
|
29332
|
+
tables: [],
|
|
29333
|
+
warning: "Cached catalog is empty. Live BigQuery discovery needs a default BigQuery connection with config.project_id."
|
|
29334
|
+
};
|
|
29335
|
+
}
|
|
29336
|
+
const region = bigQueryRegion(config.location);
|
|
29337
|
+
const schemas = include?.schemas ?? [];
|
|
29338
|
+
const schemaFilter = schemas.length ? ` AND table_schema IN (${schemas.map(sqlStringLiteral).join(", ")})` : "";
|
|
29339
|
+
const result2 = await deps.queryService.executeRawSql(`SELECT table_schema AS schema, table_name AS name FROM \`${region}\`.INFORMATION_SCHEMA.TABLES WHERE table_catalog = ${sqlStringLiteral(projectId)} AND table_type = 'BASE TABLE'${schemaFilter} ORDER BY table_schema, table_name LIMIT 1000`);
|
|
29340
|
+
return {
|
|
29341
|
+
tables: rowsToTables(result2.rows, connection2?.name ?? "default"),
|
|
29342
|
+
warning: "Cached catalog is empty; returned live BigQuery region-level INFORMATION_SCHEMA results."
|
|
29343
|
+
};
|
|
29344
|
+
}
|
|
29345
|
+
const connection = deps.configLoader.getDefaultConnection?.();
|
|
29346
|
+
if (connection?.type === "postgres" && connection.config.schema) {
|
|
29347
|
+
const result2 = await deps.queryService.executeRawSql(`SELECT table_schema AS schema, table_name AS name FROM information_schema.tables WHERE table_type IN ('BASE TABLE', 'VIEW') AND table_schema = ${sqlStringLiteral(connection.config.schema)} LIMIT 1000`);
|
|
29348
|
+
return {
|
|
29349
|
+
tables: rowsToTables(result2.rows, connection.name),
|
|
29350
|
+
warning: "Cached catalog is empty; returned live INFORMATION_SCHEMA results."
|
|
29351
|
+
};
|
|
29352
|
+
}
|
|
29353
|
+
const result = await deps.queryService.executeRawSql("SELECT table_schema AS schema, table_name AS name FROM information_schema.tables WHERE table_type IN ('BASE TABLE', 'VIEW') LIMIT 1000");
|
|
29354
|
+
return {
|
|
29355
|
+
tables: rowsToTables(result.rows, "default"),
|
|
29356
|
+
warning: "Cached catalog is empty; returned live INFORMATION_SCHEMA results."
|
|
29357
|
+
};
|
|
29358
|
+
} catch (err) {
|
|
29359
|
+
const detail2 = err instanceof Error ? err.message : String(err);
|
|
29360
|
+
return {
|
|
29361
|
+
tables: [],
|
|
29362
|
+
warning: `Cached catalog is empty and live INFORMATION_SCHEMA discovery failed: ${detail2}`
|
|
29363
|
+
};
|
|
29364
|
+
}
|
|
29365
|
+
}
|
|
29366
|
+
function rowsToTables(rows, connection) {
|
|
29367
|
+
return rows.map((r) => ({
|
|
29368
|
+
name: String(r["name"] ?? r["table_name"] ?? ""),
|
|
29369
|
+
schema: String(r["schema"] ?? r["table_schema"] ?? ""),
|
|
29370
|
+
connection,
|
|
29371
|
+
columns: []
|
|
29372
|
+
})).filter((t) => t.name.length > 0);
|
|
29373
|
+
}
|
|
29374
|
+
function sqlStringLiteral(value) {
|
|
29375
|
+
return `'${value.replace(/'/g, "''")}'`;
|
|
29376
|
+
}
|
|
29377
|
+
function bigQueryRegion(location) {
|
|
29378
|
+
const loc = (location ?? "US").trim().toLowerCase().replace(/_/g, "-");
|
|
29379
|
+
return loc.startsWith("region-") ? loc : `region-${loc}`;
|
|
29380
|
+
}
|
|
29165
29381
|
|
|
29166
29382
|
// ../../packages/mcp-core/dist/render/optionBuilder.js
|
|
29167
|
-
var SUPPORTED = /* @__PURE__ */ new Set(["line", "bar", "area", "pie", "donut", "scatter"]);
|
|
29383
|
+
var SUPPORTED = /* @__PURE__ */ new Set(["line", "bar", "area", "pie", "donut", "scatter", "kpi", "table"]);
|
|
29168
29384
|
function num(v) {
|
|
29169
29385
|
if (v === null || v === void 0 || v === "")
|
|
29170
29386
|
return null;
|
|
@@ -29184,6 +29400,64 @@ function buildEChartsOption(chart, rows) {
|
|
|
29184
29400
|
left: "center"
|
|
29185
29401
|
};
|
|
29186
29402
|
const grid = { left: "3%", right: "4%", bottom: "8%", top: 60, containLabel: true };
|
|
29403
|
+
if (type === "kpi") {
|
|
29404
|
+
const valueField = block.value?.field ?? "";
|
|
29405
|
+
const value = rows[0]?.[valueField];
|
|
29406
|
+
return {
|
|
29407
|
+
title,
|
|
29408
|
+
graphic: [
|
|
29409
|
+
{
|
|
29410
|
+
type: "text",
|
|
29411
|
+
left: "center",
|
|
29412
|
+
top: "42%",
|
|
29413
|
+
style: {
|
|
29414
|
+
text: value === null || value === void 0 ? "No data" : String(value),
|
|
29415
|
+
fontSize: 56,
|
|
29416
|
+
fontWeight: 700,
|
|
29417
|
+
fill: "#111827",
|
|
29418
|
+
textAlign: "center"
|
|
29419
|
+
}
|
|
29420
|
+
},
|
|
29421
|
+
{
|
|
29422
|
+
type: "text",
|
|
29423
|
+
left: "center",
|
|
29424
|
+
top: "58%",
|
|
29425
|
+
style: {
|
|
29426
|
+
text: block.value?.label ?? valueField,
|
|
29427
|
+
fontSize: 18,
|
|
29428
|
+
fill: "#6b7280",
|
|
29429
|
+
textAlign: "center"
|
|
29430
|
+
}
|
|
29431
|
+
}
|
|
29432
|
+
]
|
|
29433
|
+
};
|
|
29434
|
+
}
|
|
29435
|
+
if (type === "table") {
|
|
29436
|
+
const columns = block.columns?.length ? block.columns : Object.keys(rows[0] ?? {}).map((field) => ({ field, label: field }));
|
|
29437
|
+
return {
|
|
29438
|
+
title,
|
|
29439
|
+
tooltip: { trigger: "item" },
|
|
29440
|
+
grid,
|
|
29441
|
+
xAxis: {
|
|
29442
|
+
type: "category",
|
|
29443
|
+
data: columns.map((c) => c.label ?? c.field),
|
|
29444
|
+
axisLabel: { rotate: 30 }
|
|
29445
|
+
},
|
|
29446
|
+
yAxis: { type: "category", data: rows.slice(0, 10).map((_, i) => `Row ${i + 1}`) },
|
|
29447
|
+
series: [
|
|
29448
|
+
{
|
|
29449
|
+
type: "scatter",
|
|
29450
|
+
symbolSize: 1,
|
|
29451
|
+
label: {
|
|
29452
|
+
show: true,
|
|
29453
|
+
formatter: "{@[2]}",
|
|
29454
|
+
color: "#111827"
|
|
29455
|
+
},
|
|
29456
|
+
data: rows.slice(0, 10).flatMap((row, rowIndex) => columns.map((column, colIndex) => [colIndex, rowIndex, row[column.field] ?? ""]))
|
|
29457
|
+
}
|
|
29458
|
+
]
|
|
29459
|
+
};
|
|
29460
|
+
}
|
|
29187
29461
|
if (type === "pie" || type === "donut") {
|
|
29188
29462
|
return {
|
|
29189
29463
|
title,
|
|
@@ -29208,6 +29482,47 @@ function buildEChartsOption(chart, rows) {
|
|
|
29208
29482
|
};
|
|
29209
29483
|
}
|
|
29210
29484
|
const echartType = type === "bar" ? "bar" : "line";
|
|
29485
|
+
if (block.series?.columns?.length) {
|
|
29486
|
+
return {
|
|
29487
|
+
title,
|
|
29488
|
+
legend: { bottom: 0 },
|
|
29489
|
+
grid,
|
|
29490
|
+
tooltip: { trigger: "axis" },
|
|
29491
|
+
xAxis: { type: "category", data: rows.map((r) => String(r[xField] ?? "")), name: block.x?.label ?? xField },
|
|
29492
|
+
yAxis: { type: "value", name: block.y?.label ?? block.series.columns.map((c) => c.name ?? c.field).join(", ") },
|
|
29493
|
+
series: block.series.columns.map((column) => {
|
|
29494
|
+
const seriesType = column.type === "bar" ? "bar" : "line";
|
|
29495
|
+
return {
|
|
29496
|
+
name: column.name ?? column.field,
|
|
29497
|
+
type: type === "bar" ? "bar" : seriesType,
|
|
29498
|
+
data: rows.map((r) => num(r[column.field])),
|
|
29499
|
+
...type === "area" || column.type === "area" ? { areaStyle: {} } : {}
|
|
29500
|
+
};
|
|
29501
|
+
})
|
|
29502
|
+
};
|
|
29503
|
+
}
|
|
29504
|
+
if (block.series?.field) {
|
|
29505
|
+
const seriesField = block.series.field;
|
|
29506
|
+
const groups = [...new Set(rows.map((r) => String(r[seriesField] ?? "")))];
|
|
29507
|
+
const xValues = [...new Set(rows.map((r) => String(r[xField] ?? "")))];
|
|
29508
|
+
return {
|
|
29509
|
+
title,
|
|
29510
|
+
legend: { bottom: 0 },
|
|
29511
|
+
grid,
|
|
29512
|
+
tooltip: { trigger: "axis" },
|
|
29513
|
+
xAxis: { type: "category", data: xValues, name: block.x?.label ?? xField },
|
|
29514
|
+
yAxis: { type: "value", name: block.y?.label ?? yField },
|
|
29515
|
+
series: groups.map((group) => ({
|
|
29516
|
+
name: group,
|
|
29517
|
+
type: echartType,
|
|
29518
|
+
data: xValues.map((x) => {
|
|
29519
|
+
const row = rows.find((r) => String(r[xField] ?? "") === x && String(r[seriesField] ?? "") === group);
|
|
29520
|
+
return row ? num(row[yField]) : null;
|
|
29521
|
+
}),
|
|
29522
|
+
...type === "area" ? { areaStyle: {} } : {}
|
|
29523
|
+
}))
|
|
29524
|
+
};
|
|
29525
|
+
}
|
|
29211
29526
|
return {
|
|
29212
29527
|
title,
|
|
29213
29528
|
grid,
|
|
@@ -29420,6 +29735,18 @@ var EXAMPLES = {
|
|
|
29420
29735
|
y: { field: "invoiced_revenue", type: "quantitative", format: "$,.0f" }
|
|
29421
29736
|
}
|
|
29422
29737
|
},
|
|
29738
|
+
bar_multi_series_long: {
|
|
29739
|
+
name: "jobs_by_month",
|
|
29740
|
+
title: "Jobs by Month",
|
|
29741
|
+
source: { model: "jobs_by_month" },
|
|
29742
|
+
chart: {
|
|
29743
|
+
type: "bar",
|
|
29744
|
+
x: { field: "month", type: "temporal" },
|
|
29745
|
+
y: { field: "job_count", type: "quantitative", label: "Jobs" },
|
|
29746
|
+
series: { field: "job_type" },
|
|
29747
|
+
legend: { show: true, position: "bottom" }
|
|
29748
|
+
}
|
|
29749
|
+
},
|
|
29423
29750
|
line: {
|
|
29424
29751
|
name: "revenue_trend",
|
|
29425
29752
|
title: "Revenue Trend",
|
|
@@ -29430,6 +29757,22 @@ var EXAMPLES = {
|
|
|
29430
29757
|
y: { field: "invoiced_revenue", type: "quantitative" }
|
|
29431
29758
|
}
|
|
29432
29759
|
},
|
|
29760
|
+
line_multi_series_wide: {
|
|
29761
|
+
name: "jobs_completed_vs_scheduled",
|
|
29762
|
+
title: "Completed vs Scheduled Jobs",
|
|
29763
|
+
source: { model: "jobs_by_month" },
|
|
29764
|
+
chart: {
|
|
29765
|
+
type: "line",
|
|
29766
|
+
x: { field: "month", type: "temporal" },
|
|
29767
|
+
series: {
|
|
29768
|
+
columns: [
|
|
29769
|
+
{ field: "completed_jobs", name: "Completed" },
|
|
29770
|
+
{ field: "scheduled_jobs", name: "Scheduled", style: "dashed" }
|
|
29771
|
+
]
|
|
29772
|
+
},
|
|
29773
|
+
legend: { show: true, position: "bottom" }
|
|
29774
|
+
}
|
|
29775
|
+
},
|
|
29433
29776
|
area: {
|
|
29434
29777
|
name: "revenue_area",
|
|
29435
29778
|
title: "Revenue Area",
|
|
@@ -29486,7 +29829,7 @@ var EXAMPLES = {
|
|
|
29486
29829
|
function getChartSchema(args) {
|
|
29487
29830
|
const type = args.type != null && EXAMPLES[args.type] != null ? args.type : "bar";
|
|
29488
29831
|
return JSON.stringify({
|
|
29489
|
-
note: "Axes are objects: { field, type: temporal|quantitative|ordinal|nominal }. source is { model } or { sql }.",
|
|
29832
|
+
note: "Axes are objects: { field, type: temporal|quantitative|ordinal|nominal }. source is { model } or { sql }. For long-format multi-series charts use chart.series.field; for wide-format multi-series charts use chart.series.columns. chart.color is a field-name string or conditional color config, not an axis object.",
|
|
29490
29833
|
available_examples: Object.keys(EXAMPLES),
|
|
29491
29834
|
example: EXAMPLES[type]
|
|
29492
29835
|
}, null, 2);
|
|
@@ -29776,16 +30119,52 @@ ${d.content}` });
|
|
|
29776
30119
|
// ../../packages/mcp-core/dist/tools/answer.js
|
|
29777
30120
|
async function answerQuestionTool(deps, args) {
|
|
29778
30121
|
const connection = args.connection ?? deps.defaultConnectionName ?? "default";
|
|
29779
|
-
|
|
29780
|
-
|
|
29781
|
-
|
|
29782
|
-
|
|
29783
|
-
|
|
29784
|
-
|
|
29785
|
-
|
|
29786
|
-
|
|
29787
|
-
|
|
29788
|
-
|
|
30122
|
+
try {
|
|
30123
|
+
const result = await answerQuestion({
|
|
30124
|
+
question: args.question,
|
|
30125
|
+
connection,
|
|
30126
|
+
store: deps.knowledgeStore,
|
|
30127
|
+
embedder: deps.embedder,
|
|
30128
|
+
provider: deps.provider,
|
|
30129
|
+
executeRawSql: (sql) => deps.queryService.executeRawSql(sql),
|
|
30130
|
+
connectionType: deps.connectionType
|
|
30131
|
+
});
|
|
30132
|
+
return JSON.stringify(result, null, 2);
|
|
30133
|
+
} catch (err) {
|
|
30134
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
30135
|
+
const lower = message.toLowerCase();
|
|
30136
|
+
const isEmbeddingProvider = lower.includes("embedding") || lower.includes("embeddings") || lower.includes("generativelanguage.googleapis.com") || lower.includes("premature close");
|
|
30137
|
+
return JSON.stringify({
|
|
30138
|
+
error_type: isEmbeddingProvider ? "embedding_provider_unavailable" : "answer_question_failed",
|
|
30139
|
+
retryable: isEmbeddingProvider,
|
|
30140
|
+
fallback_available: true,
|
|
30141
|
+
fallback_attempted: false,
|
|
30142
|
+
...suggestFallback(deps, args.question),
|
|
30143
|
+
message: isEmbeddingProvider ? "Natural-language retrieval failed, but governed metric, model, chart, and SQL tools are available." : "Natural-language answering failed, but governed metric, model, chart, and SQL tools are available.",
|
|
30144
|
+
detail: message
|
|
30145
|
+
}, null, 2);
|
|
30146
|
+
}
|
|
30147
|
+
}
|
|
30148
|
+
function suggestFallback(deps, question) {
|
|
30149
|
+
const charts = deps.configLoader?.getCharts?.() ?? [];
|
|
30150
|
+
const models = deps.configLoader?.getModels?.() ?? [];
|
|
30151
|
+
const terms = question.toLowerCase().split(/[^a-z0-9_]+/).filter((t) => t.length >= 3);
|
|
30152
|
+
const score = (text2) => terms.reduce((total, term) => total + (text2.includes(term) ? 1 : 0), 0);
|
|
30153
|
+
const rankedChart = charts.map((chart) => ({
|
|
30154
|
+
chart,
|
|
30155
|
+
score: score(`${chart.name} ${chart.title ?? ""} ${chart.source?.model ?? ""}`.toLowerCase())
|
|
30156
|
+
})).sort((a, b) => b.score - a.score)[0];
|
|
30157
|
+
if (rankedChart && rankedChart.score > 0) {
|
|
30158
|
+
return { suggested_tool: "run_model", suggested_args: { name: rankedChart.chart.name } };
|
|
30159
|
+
}
|
|
30160
|
+
const rankedModel = models.map((model) => ({
|
|
30161
|
+
model,
|
|
30162
|
+
score: score(`${model.metadata.name} ${model.metadata.description ?? ""}`.toLowerCase())
|
|
30163
|
+
})).sort((a, b) => b.score - a.score)[0];
|
|
30164
|
+
if (rankedModel && rankedModel.score > 0) {
|
|
30165
|
+
return { suggested_tool: "run_sql", suggested_args: { sql: rankedModel.model.sql } };
|
|
30166
|
+
}
|
|
30167
|
+
return {};
|
|
29789
30168
|
}
|
|
29790
30169
|
|
|
29791
30170
|
// ../../packages/mcp-core/dist/render/embedResource.js
|
|
@@ -29878,17 +30257,30 @@ var text = (s) => ({
|
|
|
29878
30257
|
content: [{ type: "text", text: s }],
|
|
29879
30258
|
...s.startsWith("Error:") ? { isError: true } : {}
|
|
29880
30259
|
});
|
|
30260
|
+
var textOut = (s) => ({
|
|
30261
|
+
content: [{ type: "text", text: s }],
|
|
30262
|
+
structuredContent: { result: s },
|
|
30263
|
+
...s.startsWith("Error:") ? { isError: true } : {}
|
|
30264
|
+
});
|
|
30265
|
+
var TABULAR_OUTPUT = {
|
|
30266
|
+
rowCount: z2.number(),
|
|
30267
|
+
columns: z2.array(z2.string()),
|
|
30268
|
+
rows: z2.array(z2.record(z2.unknown())),
|
|
30269
|
+
preview: z2.array(z2.record(z2.unknown()))
|
|
30270
|
+
};
|
|
30271
|
+
var TEXT_OUTPUT = { result: z2.string() };
|
|
29881
30272
|
var READ_ONLY = { readOnlyHint: true, destructiveHint: false, openWorldHint: false };
|
|
29882
30273
|
var WRITE_SAFE = { readOnlyHint: false, destructiveHint: false, openWorldHint: false };
|
|
29883
30274
|
function buildResourceMeta(baseUrl) {
|
|
30275
|
+
const RESOURCE_DOMAINS = ["https://cdn.jsdelivr.net", "https://persistent.oaistatic.com"];
|
|
30276
|
+
const csp = { connectDomains: [baseUrl], resourceDomains: RESOURCE_DOMAINS };
|
|
29884
30277
|
return {
|
|
29885
|
-
|
|
29886
|
-
|
|
29887
|
-
|
|
29888
|
-
|
|
29889
|
-
|
|
29890
|
-
|
|
29891
|
-
}
|
|
30278
|
+
// OpenAI Apps SDK (ChatGPT submission) — clears "Widget CSP / domain not set".
|
|
30279
|
+
"openai/widgetCSP": { connect_domains: [baseUrl], resource_domains: RESOURCE_DOMAINS },
|
|
30280
|
+
"openai/widgetDomain": baseUrl,
|
|
30281
|
+
"openai/widgetDescription": "Interactive yamchart chart renderer",
|
|
30282
|
+
// Standard MCP-UI camelCase form (kept for forward-compat).
|
|
30283
|
+
ui: { domain: baseUrl, csp }
|
|
29892
30284
|
};
|
|
29893
30285
|
}
|
|
29894
30286
|
function createMcpServer(deps) {
|
|
@@ -29900,11 +30292,21 @@ function createMcpServer(deps) {
|
|
|
29900
30292
|
_meta: buildResourceMeta(baseUrl)
|
|
29901
30293
|
}, async () => {
|
|
29902
30294
|
const tpl = await buildTemplateResource();
|
|
29903
|
-
return {
|
|
30295
|
+
return {
|
|
30296
|
+
contents: [
|
|
30297
|
+
{
|
|
30298
|
+
uri: TEMPLATE_URI,
|
|
30299
|
+
mimeType: tpl.resource.mimeType,
|
|
30300
|
+
text: tpl.resource.text ?? "",
|
|
30301
|
+
_meta: buildResourceMeta(baseUrl)
|
|
30302
|
+
}
|
|
30303
|
+
]
|
|
30304
|
+
};
|
|
29904
30305
|
});
|
|
29905
30306
|
server.registerTool("list_charts", {
|
|
29906
30307
|
description: "List governed yamchart charts (name, title, type, model, parameters).",
|
|
29907
30308
|
inputSchema: {},
|
|
30309
|
+
outputSchema: { charts: z2.array(z2.unknown()) },
|
|
29908
30310
|
annotations: READ_ONLY
|
|
29909
30311
|
}, async () => {
|
|
29910
30312
|
const raw = await listCharts(deps);
|
|
@@ -29917,6 +30319,7 @@ function createMcpServer(deps) {
|
|
|
29917
30319
|
server.registerTool("list_models", {
|
|
29918
30320
|
description: "List governed SQL models (name, description).",
|
|
29919
30321
|
inputSchema: {},
|
|
30322
|
+
outputSchema: { models: z2.array(z2.unknown()) },
|
|
29920
30323
|
annotations: READ_ONLY
|
|
29921
30324
|
}, async () => {
|
|
29922
30325
|
const raw = await listModels(deps);
|
|
@@ -29927,8 +30330,9 @@ function createMcpServer(deps) {
|
|
|
29927
30330
|
};
|
|
29928
30331
|
});
|
|
29929
30332
|
server.registerTool("run_model", {
|
|
29930
|
-
description: "
|
|
30333
|
+
description: "Execute a saved governed chart by its chart name (from list_charts) with parameters and return its data. Note: this runs CHARTS, not models \u2014 passing a model name returns an actionable error listing the chart names defined for that model.",
|
|
29931
30334
|
inputSchema: { name: z2.string(), params: z2.record(z2.unknown()).optional() },
|
|
30335
|
+
outputSchema: TABULAR_OUTPUT,
|
|
29932
30336
|
annotations: READ_ONLY
|
|
29933
30337
|
}, async (args) => {
|
|
29934
30338
|
const raw = await runModel(deps, args);
|
|
@@ -29940,6 +30344,7 @@ function createMcpServer(deps) {
|
|
|
29940
30344
|
structuredContent: {
|
|
29941
30345
|
rowCount: parsed.rows.length,
|
|
29942
30346
|
columns: parsed.columns.map((c) => c.name),
|
|
30347
|
+
rows: parsed.rows,
|
|
29943
30348
|
preview: parsed.rows.slice(0, 5)
|
|
29944
30349
|
}
|
|
29945
30350
|
};
|
|
@@ -29947,6 +30352,7 @@ function createMcpServer(deps) {
|
|
|
29947
30352
|
server.registerTool("run_sql", {
|
|
29948
30353
|
description: "Run a read-only ad-hoc SQL query (destructive statements blocked, row-capped).",
|
|
29949
30354
|
inputSchema: { sql: z2.string(), limit: z2.number().optional() },
|
|
30355
|
+
outputSchema: TABULAR_OUTPUT,
|
|
29950
30356
|
annotations: READ_ONLY
|
|
29951
30357
|
}, async (args) => {
|
|
29952
30358
|
const raw = await runSql(deps, args);
|
|
@@ -29958,6 +30364,7 @@ function createMcpServer(deps) {
|
|
|
29958
30364
|
structuredContent: {
|
|
29959
30365
|
rowCount: parsed.rows.length,
|
|
29960
30366
|
columns: parsed.columns.map((c) => c.name),
|
|
30367
|
+
rows: parsed.rows,
|
|
29961
30368
|
preview: parsed.rows.slice(0, 5)
|
|
29962
30369
|
}
|
|
29963
30370
|
};
|
|
@@ -29965,23 +30372,26 @@ function createMcpServer(deps) {
|
|
|
29965
30372
|
server.registerTool("list_tables", {
|
|
29966
30373
|
description: "List warehouse tables from the cached catalog (name, schema, connection, column count).",
|
|
29967
30374
|
inputSchema: {},
|
|
30375
|
+
outputSchema: { tables: z2.array(z2.unknown()), warning: z2.string().optional() },
|
|
29968
30376
|
annotations: READ_ONLY
|
|
29969
30377
|
}, async () => {
|
|
29970
30378
|
const raw = await listTables(deps);
|
|
29971
|
-
const
|
|
30379
|
+
const parsed = JSON.parse(raw);
|
|
29972
30380
|
return {
|
|
29973
30381
|
content: [{ type: "text", text: raw }],
|
|
29974
|
-
structuredContent:
|
|
30382
|
+
structuredContent: parsed
|
|
29975
30383
|
};
|
|
29976
30384
|
});
|
|
29977
30385
|
server.registerTool("describe_table", {
|
|
29978
30386
|
description: 'Show columns and types for a catalog table. Accepts { table } (with optional dot-notation schema: "schema.table") or { table, schema } separately.',
|
|
29979
30387
|
inputSchema: { table: z2.string(), schema: z2.string().optional() },
|
|
30388
|
+
outputSchema: TEXT_OUTPUT,
|
|
29980
30389
|
annotations: READ_ONLY
|
|
29981
|
-
}, async (args) =>
|
|
30390
|
+
}, async (args) => textOut(await describeTable(deps, args)));
|
|
29982
30391
|
server.registerTool("sample_table", {
|
|
29983
30392
|
description: "Return a few sample rows from a table.",
|
|
29984
|
-
inputSchema: { table: z2.string(), limit: z2.number().optional() },
|
|
30393
|
+
inputSchema: { table: z2.string(), schema: z2.string().optional(), limit: z2.number().optional() },
|
|
30394
|
+
outputSchema: TABULAR_OUTPUT,
|
|
29985
30395
|
annotations: READ_ONLY
|
|
29986
30396
|
}, async (args) => {
|
|
29987
30397
|
const raw = await sampleTable(deps, args);
|
|
@@ -29993,6 +30403,7 @@ function createMcpServer(deps) {
|
|
|
29993
30403
|
structuredContent: {
|
|
29994
30404
|
rowCount: parsed.rows.length,
|
|
29995
30405
|
columns: parsed.columns.map((c) => c.name),
|
|
30406
|
+
rows: parsed.rows,
|
|
29996
30407
|
preview: parsed.rows.slice(0, 5)
|
|
29997
30408
|
}
|
|
29998
30409
|
};
|
|
@@ -30000,6 +30411,7 @@ function createMcpServer(deps) {
|
|
|
30000
30411
|
server.registerTool("visualize", {
|
|
30001
30412
|
description: "Render a yamchart chart spec + data rows to an interactive chart (with a static image fallback). Provide a full chart spec (name, title, source.model, chart) and the data array.",
|
|
30002
30413
|
inputSchema: { spec: z2.record(z2.unknown()), data: z2.array(z2.record(z2.unknown())) },
|
|
30414
|
+
outputSchema: { spec: z2.record(z2.unknown()), option: z2.record(z2.unknown()) },
|
|
30003
30415
|
annotations: READ_ONLY,
|
|
30004
30416
|
_meta: {
|
|
30005
30417
|
ui: { resourceUri: TEMPLATE_URI },
|
|
@@ -30020,16 +30432,47 @@ function createMcpServer(deps) {
|
|
|
30020
30432
|
structuredContent: { spec: result.spec, option: result.option }
|
|
30021
30433
|
};
|
|
30022
30434
|
});
|
|
30435
|
+
server.registerTool("get_permissions", {
|
|
30436
|
+
description: "Return the current MCP user role and permissions, including whether save_chart is allowed.",
|
|
30437
|
+
inputSchema: {},
|
|
30438
|
+
outputSchema: {
|
|
30439
|
+
role: z2.string(),
|
|
30440
|
+
permissions: z2.record(z2.boolean()),
|
|
30441
|
+
required_role: z2.object({ save_chart: z2.string() })
|
|
30442
|
+
},
|
|
30443
|
+
annotations: READ_ONLY
|
|
30444
|
+
}, async () => {
|
|
30445
|
+
const role = deps.user?.role ?? "anonymous";
|
|
30446
|
+
const permissions = {
|
|
30447
|
+
can_view_charts: true,
|
|
30448
|
+
can_run_sql: true,
|
|
30449
|
+
can_validate_charts: true,
|
|
30450
|
+
can_visualize: true,
|
|
30451
|
+
can_save_charts: canWrite(deps.user)
|
|
30452
|
+
};
|
|
30453
|
+
const result = {
|
|
30454
|
+
role,
|
|
30455
|
+
permissions,
|
|
30456
|
+
required_role: { save_chart: "editor_or_admin" }
|
|
30457
|
+
};
|
|
30458
|
+
const raw = JSON.stringify(result);
|
|
30459
|
+
return {
|
|
30460
|
+
content: [{ type: "text", text: raw }],
|
|
30461
|
+
structuredContent: result
|
|
30462
|
+
};
|
|
30463
|
+
});
|
|
30023
30464
|
server.registerTool("save_chart", {
|
|
30024
30465
|
description: "Save a yamchart chart spec to charts/<name>.yaml (requires editor/admin).",
|
|
30025
30466
|
inputSchema: { spec: z2.record(z2.unknown()) },
|
|
30467
|
+
outputSchema: TEXT_OUTPUT,
|
|
30026
30468
|
annotations: WRITE_SAFE
|
|
30027
|
-
}, async (args) =>
|
|
30469
|
+
}, async (args) => textOut(await saveChart(deps, args)));
|
|
30028
30470
|
if (deps.semanticLayer) {
|
|
30029
30471
|
const semanticLayer = deps.semanticLayer;
|
|
30030
30472
|
server.registerTool("list_metrics", {
|
|
30031
30473
|
description: "List governed business metrics (the semantic glossary): name, synonyms, the entity they belong to, and which dimensions you can slice them by. Prefer querying these by name over writing raw SQL.",
|
|
30032
30474
|
inputSchema: {},
|
|
30475
|
+
outputSchema: { metrics: z2.array(z2.unknown()) },
|
|
30033
30476
|
annotations: READ_ONLY
|
|
30034
30477
|
}, async () => {
|
|
30035
30478
|
const raw = listMetrics({ semanticLayer });
|
|
@@ -30042,8 +30485,9 @@ function createMcpServer(deps) {
|
|
|
30042
30485
|
server.registerTool("get_metric", {
|
|
30043
30486
|
description: "Get the full governed definition of a metric (by name or synonym): how it is computed (aggregation, field/sql, governed filter), its source table, format, and the dimensions/time dimension you can group it by.",
|
|
30044
30487
|
inputSchema: { name: z2.string() },
|
|
30488
|
+
outputSchema: TEXT_OUTPUT,
|
|
30045
30489
|
annotations: READ_ONLY
|
|
30046
|
-
}, async (args) =>
|
|
30490
|
+
}, async (args) => textOut(getMetric({ semanticLayer }, args)));
|
|
30047
30491
|
server.registerTool("query_metric", {
|
|
30048
30492
|
description: "Query governed metrics by name (or synonym), optionally sliced by dimensions and a time grain, and filtered by a time range. Returns the data rows, the generated SQL (for transparency), and a ready-to-use suggested_chart spec. Prefer this over run_sql for business questions about governed metrics \u2014 it guarantees correct, governed definitions.",
|
|
30049
30493
|
inputSchema: {
|
|
@@ -30058,12 +30502,20 @@ function createMcpServer(deps) {
|
|
|
30058
30502
|
order_by: z2.object({ field: z2.string(), direction: z2.enum(["asc", "desc"]).optional() }).optional(),
|
|
30059
30503
|
limit: z2.number().int().positive().max(1e4).optional()
|
|
30060
30504
|
},
|
|
30505
|
+
outputSchema: {
|
|
30506
|
+
rowCount: z2.number(),
|
|
30507
|
+
columns: z2.array(z2.string()),
|
|
30508
|
+
rows: z2.array(z2.record(z2.unknown())),
|
|
30509
|
+
preview: z2.array(z2.record(z2.unknown())),
|
|
30510
|
+
sql: z2.string(),
|
|
30511
|
+
suggested_chart: z2.unknown()
|
|
30512
|
+
},
|
|
30061
30513
|
annotations: READ_ONLY
|
|
30062
30514
|
}, async (args) => {
|
|
30063
30515
|
try {
|
|
30064
30516
|
const raw = await queryMetric({ semanticLayer, queryService: deps.queryService, connectionType: deps.connectionType }, args);
|
|
30065
30517
|
if (raw.startsWith("Error:") || raw.startsWith("{") && JSON.parse(raw).error) {
|
|
30066
|
-
return text
|
|
30518
|
+
return { content: [{ type: "text", text: raw }], isError: true };
|
|
30067
30519
|
}
|
|
30068
30520
|
const parsed = JSON.parse(raw);
|
|
30069
30521
|
return {
|
|
@@ -30071,6 +30523,7 @@ function createMcpServer(deps) {
|
|
|
30071
30523
|
structuredContent: {
|
|
30072
30524
|
rowCount: parsed.rows.length,
|
|
30073
30525
|
columns: parsed.columns.map((c) => c.name),
|
|
30526
|
+
rows: parsed.rows,
|
|
30074
30527
|
preview: parsed.rows.slice(0, 5),
|
|
30075
30528
|
sql: parsed.sql,
|
|
30076
30529
|
suggested_chart: parsed.suggested_chart
|
|
@@ -30088,12 +30541,21 @@ function createMcpServer(deps) {
|
|
|
30088
30541
|
server.registerTool("answer_question", {
|
|
30089
30542
|
description: 'Answer a business question using governed knowledge: retrieves confirmed Hunches + business context for the source and either reuses a matching SQL or generates SQL grounded in trusted examples. Prefer this for "how many / how much / which" questions.',
|
|
30090
30543
|
inputSchema: { question: z2.string(), connection: z2.string().optional() },
|
|
30544
|
+
outputSchema: {
|
|
30545
|
+
rowCount: z2.number(),
|
|
30546
|
+
columns: z2.array(z2.string()),
|
|
30547
|
+
rows: z2.array(z2.record(z2.unknown())),
|
|
30548
|
+
preview: z2.array(z2.record(z2.unknown())),
|
|
30549
|
+
sql: z2.unknown().optional(),
|
|
30550
|
+
answer: z2.unknown().optional()
|
|
30551
|
+
},
|
|
30091
30552
|
annotations: READ_ONLY
|
|
30092
30553
|
}, async (args) => {
|
|
30093
30554
|
const raw = await answerQuestionTool({
|
|
30094
30555
|
knowledgeStore,
|
|
30095
30556
|
embedder,
|
|
30096
30557
|
provider: answerProvider,
|
|
30558
|
+
configLoader: deps.configLoader,
|
|
30097
30559
|
queryService: deps.queryService,
|
|
30098
30560
|
connectionType: deps.connectionType,
|
|
30099
30561
|
defaultConnectionName: deps.aiConnectionName
|
|
@@ -30101,6 +30563,9 @@ function createMcpServer(deps) {
|
|
|
30101
30563
|
if (raw.startsWith("Error:"))
|
|
30102
30564
|
return { content: [{ type: "text", text: raw }], isError: true };
|
|
30103
30565
|
const parsed = JSON.parse(raw);
|
|
30566
|
+
if (parsed["error_type"]) {
|
|
30567
|
+
return { content: [{ type: "text", text: raw }], isError: true };
|
|
30568
|
+
}
|
|
30104
30569
|
const rows = parsed.rows ?? [];
|
|
30105
30570
|
const columns = parsed.columns ?? [];
|
|
30106
30571
|
return {
|
|
@@ -30108,6 +30573,7 @@ function createMcpServer(deps) {
|
|
|
30108
30573
|
structuredContent: {
|
|
30109
30574
|
rowCount: rows.length,
|
|
30110
30575
|
columns: columns.map((c) => c.name),
|
|
30576
|
+
rows,
|
|
30111
30577
|
preview: rows.slice(0, 5),
|
|
30112
30578
|
sql: parsed.sql,
|
|
30113
30579
|
answer: parsed.answer
|
|
@@ -30118,18 +30584,21 @@ function createMcpServer(deps) {
|
|
|
30118
30584
|
server.registerTool("get_chart_schema", {
|
|
30119
30585
|
description: "Get the exact yamchart chart spec shape and a working example for a chart type (bar, line, area, kpi, pie, scatter, table). Call this before building a chart. Axes are objects: { field, type: temporal|quantitative|ordinal|nominal }.",
|
|
30120
30586
|
inputSchema: { type: z2.string().optional() },
|
|
30587
|
+
outputSchema: TEXT_OUTPUT,
|
|
30121
30588
|
annotations: READ_ONLY
|
|
30122
|
-
}, async (args) =>
|
|
30589
|
+
}, async (args) => textOut(getChartSchema(args)));
|
|
30123
30590
|
server.registerTool("validate_chart", {
|
|
30124
30591
|
description: "Validate a yamchart chart spec without saving it. Returns { valid: true } or field-level errors with hints so you can self-correct before calling save_chart or visualize.",
|
|
30125
30592
|
inputSchema: { spec: z2.record(z2.unknown()) },
|
|
30593
|
+
outputSchema: TEXT_OUTPUT,
|
|
30126
30594
|
annotations: READ_ONLY
|
|
30127
|
-
}, async (args) =>
|
|
30595
|
+
}, async (args) => textOut(validateChart(args)));
|
|
30128
30596
|
server.registerTool("get_chart_spec", {
|
|
30129
30597
|
description: "Get the full saved spec of an existing governed chart by name (so you can copy or adapt it). Call list_charts to discover names.",
|
|
30130
30598
|
inputSchema: { name: z2.string() },
|
|
30599
|
+
outputSchema: TEXT_OUTPUT,
|
|
30131
30600
|
annotations: READ_ONLY
|
|
30132
|
-
}, async (args) =>
|
|
30601
|
+
}, async (args) => textOut(getChartSpec({ configLoader: deps.configLoader }, args)));
|
|
30133
30602
|
return server;
|
|
30134
30603
|
}
|
|
30135
30604
|
|
|
@@ -30227,6 +30696,22 @@ async function catalogRoutes(fastify, options) {
|
|
|
30227
30696
|
});
|
|
30228
30697
|
}
|
|
30229
30698
|
|
|
30699
|
+
// ../server/dist/services/dbt-path.js
|
|
30700
|
+
import { readFile as readFile6 } from "fs/promises";
|
|
30701
|
+
import { join as join13, isAbsolute as isAbsolute2, resolve as resolve2 } from "path";
|
|
30702
|
+
import { parse as parseYaml3 } from "yaml";
|
|
30703
|
+
async function resolveDbtProjectPath(projectDir) {
|
|
30704
|
+
try {
|
|
30705
|
+
const raw = await readFile6(join13(projectDir, ".yamchart", "dbt-source.yaml"), "utf-8");
|
|
30706
|
+
const parsed = parseYaml3(raw);
|
|
30707
|
+
if (!parsed?.path)
|
|
30708
|
+
return null;
|
|
30709
|
+
return isAbsolute2(parsed.path) ? parsed.path : resolve2(projectDir, parsed.path);
|
|
30710
|
+
} catch {
|
|
30711
|
+
return null;
|
|
30712
|
+
}
|
|
30713
|
+
}
|
|
30714
|
+
|
|
30230
30715
|
// ../server/dist/routes/learning.js
|
|
30231
30716
|
var ReadOnlySqlError = class extends Error {
|
|
30232
30717
|
constructor() {
|
|
@@ -30265,6 +30750,51 @@ async function answerBlindspot(deps, id, body) {
|
|
|
30265
30750
|
async function dismissBlindspot(deps, id) {
|
|
30266
30751
|
return deps.store.updateBlindspot(id, { status: "dismissed" });
|
|
30267
30752
|
}
|
|
30753
|
+
async function acceptBlindspot(deps, id) {
|
|
30754
|
+
const bs = await deps.store.getBlindspot(id);
|
|
30755
|
+
if (!bs || bs.kind !== "opportunity" || !bs.suggestedModel) {
|
|
30756
|
+
return { status: 400, body: { error: "Not an opportunity blindspot with a suggested model." } };
|
|
30757
|
+
}
|
|
30758
|
+
if (!/^[a-zA-Z0-9_]+$/.test(bs.suggestedModel.name)) {
|
|
30759
|
+
return {
|
|
30760
|
+
status: 400,
|
|
30761
|
+
body: { error: "Invalid suggested model name \u2014 dbt model names may contain only letters, digits, and underscores." }
|
|
30762
|
+
};
|
|
30763
|
+
}
|
|
30764
|
+
const dbtPath = await resolveDbtProjectPath(deps.projectDir);
|
|
30765
|
+
if (!dbtPath) {
|
|
30766
|
+
return {
|
|
30767
|
+
status: 400,
|
|
30768
|
+
body: { error: 'No connected dbt project. Run "yamchart sync-dbt" to link one.' }
|
|
30769
|
+
};
|
|
30770
|
+
}
|
|
30771
|
+
try {
|
|
30772
|
+
const sm = bs.suggestedModel;
|
|
30773
|
+
const conventions = await detectConventions(dbtPath);
|
|
30774
|
+
const modelPath = buildModelPath(sm.name, void 0, conventions);
|
|
30775
|
+
const layer = modelPath.split("/")[1] ?? "marts";
|
|
30776
|
+
const materialization = conventions.commonMaterializations[layer] ?? "view";
|
|
30777
|
+
const sql = formatModelSql({
|
|
30778
|
+
materialization,
|
|
30779
|
+
sql: scaffoldModelSql(sm),
|
|
30780
|
+
description: sm.description
|
|
30781
|
+
});
|
|
30782
|
+
await writeDbtModel(dbtPath, modelPath, sql);
|
|
30783
|
+
const schemaPath = modelPath.replace(/[^/]+\.sql$/, "schema.yml");
|
|
30784
|
+
await updateSchemaYml(dbtPath, schemaPath, {
|
|
30785
|
+
name: sm.name,
|
|
30786
|
+
description: sm.description,
|
|
30787
|
+
columns: sm.columns
|
|
30788
|
+
});
|
|
30789
|
+
const blindspot = await deps.store.updateBlindspot(id, {
|
|
30790
|
+
status: "answered",
|
|
30791
|
+
answer: `Scaffolded dbt model: ${modelPath}`
|
|
30792
|
+
});
|
|
30793
|
+
return { status: 200, body: { modelPath, schemaPath, blindspot } };
|
|
30794
|
+
} catch (e) {
|
|
30795
|
+
return { status: 500, body: { error: e.message } };
|
|
30796
|
+
}
|
|
30797
|
+
}
|
|
30268
30798
|
async function loadContextBlock(opts, store, connection) {
|
|
30269
30799
|
const fileDocs = loadKnowledgeDocs(opts.projectDir);
|
|
30270
30800
|
const dbDocs = (await store.listKnowledgeDocs({ connection })).map((d) => `${d.title}
|
|
@@ -30460,6 +30990,16 @@ async function learningRoutes(fastify, opts) {
|
|
|
30460
30990
|
}
|
|
30461
30991
|
return dismissBlindspot({ store }, request.params.id);
|
|
30462
30992
|
});
|
|
30993
|
+
fastify.post("/api/blindspots/:id/accept", async (request, reply) => {
|
|
30994
|
+
if (!requireStore(reply))
|
|
30995
|
+
return;
|
|
30996
|
+
if (!opts.canWrite(request)) {
|
|
30997
|
+
reply.code(403).send({ error: "Requires editor or admin role." });
|
|
30998
|
+
return;
|
|
30999
|
+
}
|
|
31000
|
+
const result = await acceptBlindspot({ store, projectDir: opts.projectDir }, request.params.id);
|
|
31001
|
+
reply.code(result.status).send(result.body);
|
|
31002
|
+
});
|
|
30463
31003
|
fastify.get("/api/coverage", async (request, reply) => {
|
|
30464
31004
|
if (!requireStore(reply))
|
|
30465
31005
|
return;
|
|
@@ -31355,18 +31895,18 @@ data: ${data}
|
|
|
31355
31895
|
}
|
|
31356
31896
|
|
|
31357
31897
|
// ../server/dist/server.js
|
|
31358
|
-
import { join as
|
|
31898
|
+
import { join as join14, dirname as dirname4 } from "path";
|
|
31359
31899
|
import { fileURLToPath } from "url";
|
|
31360
|
-
import { access as access2, readFile as
|
|
31900
|
+
import { access as access2, readFile as readFile7 } from "fs/promises";
|
|
31361
31901
|
import { watch as fsWatch } from "fs";
|
|
31362
31902
|
var __dirname = dirname4(fileURLToPath(import.meta.url));
|
|
31363
|
-
var packageJsonPath =
|
|
31364
|
-
var packageJson = JSON.parse(await
|
|
31903
|
+
var packageJsonPath = join14(__dirname, "..", "package.json");
|
|
31904
|
+
var packageJson = JSON.parse(await readFile7(packageJsonPath, "utf-8"));
|
|
31365
31905
|
var VERSION = packageJson.version;
|
|
31366
31906
|
async function enrichRefsFromCatalog(projectDir, refs) {
|
|
31367
31907
|
try {
|
|
31368
|
-
const catalogPath =
|
|
31369
|
-
const catalogData = JSON.parse(await
|
|
31908
|
+
const catalogPath = join14(projectDir, ".yamchart", "catalog.json");
|
|
31909
|
+
const catalogData = JSON.parse(await readFile7(catalogPath, "utf-8"));
|
|
31370
31910
|
for (const model of catalogData.models ?? []) {
|
|
31371
31911
|
if (model.name && model.table) {
|
|
31372
31912
|
refs[model.name] = model.table;
|
|
@@ -31376,7 +31916,7 @@ async function enrichRefsFromCatalog(projectDir, refs) {
|
|
|
31376
31916
|
}
|
|
31377
31917
|
}
|
|
31378
31918
|
async function createServer(options) {
|
|
31379
|
-
const { projectDir, port = 3001, host = "0.0.0.0", watch: watch2 = false, serveStatic = process.env.NODE_ENV === "production", staticDir =
|
|
31919
|
+
const { projectDir, port = 3001, host = "0.0.0.0", watch: watch2 = false, serveStatic = process.env.NODE_ENV === "production", staticDir = join14(__dirname, "public"), auth } = options;
|
|
31380
31920
|
if (auth) {
|
|
31381
31921
|
initAuthServer(auth.supabaseUrl, auth.supabaseServiceKey);
|
|
31382
31922
|
}
|
|
@@ -31639,9 +32179,9 @@ async function createServer(options) {
|
|
|
31639
32179
|
dialect: defaultConnection?.type ?? "duckdb",
|
|
31640
32180
|
metricNames: semanticLayer?.listMetrics().map((m) => m.name) ?? [],
|
|
31641
32181
|
getConnectionTables: async (connection) => {
|
|
31642
|
-
const catalogPath =
|
|
32182
|
+
const catalogPath = join14(projectDir, ".yamchart", "catalog.json");
|
|
31643
32183
|
try {
|
|
31644
|
-
const catalogData = JSON.parse(await
|
|
32184
|
+
const catalogData = JSON.parse(await readFile7(catalogPath, "utf-8"));
|
|
31645
32185
|
return (catalogData.models ?? []).filter((m) => !m.connection || m.connection === connection).map((m) => ({
|
|
31646
32186
|
table: m.table ?? m.name ?? "",
|
|
31647
32187
|
columns: (m.columns ?? []).map((c) => ({ name: c.name, type: c.data_type ?? "string" }))
|
|
@@ -31756,7 +32296,7 @@ async function createServer(options) {
|
|
|
31756
32296
|
await fastify.register(adminConfigRoutes, adminConfigOptions);
|
|
31757
32297
|
await registerReconnect(fastify);
|
|
31758
32298
|
}
|
|
31759
|
-
const assetsDir =
|
|
32299
|
+
const assetsDir = join14(projectDir, "assets");
|
|
31760
32300
|
try {
|
|
31761
32301
|
await access2(assetsDir);
|
|
31762
32302
|
await fastify.register(fastifyStatic, {
|
|
@@ -31767,10 +32307,10 @@ async function createServer(options) {
|
|
|
31767
32307
|
} catch {
|
|
31768
32308
|
}
|
|
31769
32309
|
if (project.theme?.customCss) {
|
|
31770
|
-
const cssPath =
|
|
32310
|
+
const cssPath = join14(projectDir, project.theme.customCss);
|
|
31771
32311
|
fastify.get("/api/theme/custom.css", async (_request2, reply) => {
|
|
31772
32312
|
try {
|
|
31773
|
-
const content = await
|
|
32313
|
+
const content = await readFile7(cssPath, "utf-8");
|
|
31774
32314
|
return reply.type("text/css").send(content);
|
|
31775
32315
|
} catch {
|
|
31776
32316
|
return reply.status(404).send("");
|
|
@@ -31827,7 +32367,7 @@ async function createServer(options) {
|
|
|
31827
32367
|
fastify.log.info(`Semantic model rebuilt: ${semanticService.getModel()?.entities.length ?? 0} entities`);
|
|
31828
32368
|
broadcastConfigChange();
|
|
31829
32369
|
});
|
|
31830
|
-
const catalogPath =
|
|
32370
|
+
const catalogPath = join14(projectDir, ".yamchart", "catalog.json");
|
|
31831
32371
|
try {
|
|
31832
32372
|
catalogWatcher = fsWatch(catalogPath, () => {
|
|
31833
32373
|
semanticService.buildFromDisk();
|
|
@@ -31893,13 +32433,13 @@ async function runDevServer(projectDir, options) {
|
|
|
31893
32433
|
try {
|
|
31894
32434
|
const { readFileSync: readFileSync5 } = await import("fs");
|
|
31895
32435
|
const { parse: parse2 } = await import("yaml");
|
|
31896
|
-
const { resolveProjectConfig: resolveProjectConfig2, deepMerge: deepMerge3 } = await import("./dist-
|
|
31897
|
-
const raw = readFileSync5(
|
|
32436
|
+
const { resolveProjectConfig: resolveProjectConfig2, deepMerge: deepMerge3 } = await import("./dist-F6TVLP6C.js");
|
|
32437
|
+
const raw = readFileSync5(resolve3(projectDir, "yamchart.yaml"), "utf-8");
|
|
31898
32438
|
const rawConfig = parse2(raw);
|
|
31899
32439
|
const resolvedEnv = options.env || process.env.YAMCHART_ENV || void 0;
|
|
31900
32440
|
let projectConfig = resolveProjectConfig2(rawConfig, resolvedEnv);
|
|
31901
32441
|
try {
|
|
31902
|
-
const localRaw = readFileSync5(
|
|
32442
|
+
const localRaw = readFileSync5(resolve3(projectDir, "yamchart.local.yaml"), "utf-8");
|
|
31903
32443
|
const localOverrides = parse2(localRaw);
|
|
31904
32444
|
if (localOverrides && typeof localOverrides === "object") {
|
|
31905
32445
|
projectConfig = deepMerge3(projectConfig, localOverrides);
|
|
@@ -31909,15 +32449,15 @@ async function runDevServer(projectDir, options) {
|
|
|
31909
32449
|
if (projectConfig?.auth?.enabled) {
|
|
31910
32450
|
localAuth = {
|
|
31911
32451
|
enabled: true,
|
|
31912
|
-
dbPath: projectConfig.auth.db_path ?
|
|
32452
|
+
dbPath: projectConfig.auth.db_path ? resolve3(projectConfig.auth.db_path.replace(/^~/, homedir())) : resolve3(homedir(), ".yamchart", "auth.db"),
|
|
31913
32453
|
sessionTtlMs: projectConfig.auth.session_ttl ? parseTtl(projectConfig.auth.session_ttl) : 30 * 24 * 60 * 60 * 1e3
|
|
31914
32454
|
};
|
|
31915
32455
|
}
|
|
31916
|
-
const connectionDir =
|
|
32456
|
+
const connectionDir = resolve3(projectDir, "connections");
|
|
31917
32457
|
const targetConn = process.env.YAMCHART_CONNECTION || projectConfig?.defaults?.connection;
|
|
31918
32458
|
if (targetConn) {
|
|
31919
32459
|
try {
|
|
31920
|
-
const connRaw = readFileSync5(
|
|
32460
|
+
const connRaw = readFileSync5(resolve3(connectionDir, `${targetConn}.yaml`), "utf-8");
|
|
31921
32461
|
const connConfig = parse2(connRaw);
|
|
31922
32462
|
if (connConfig?.auth?.type === "externalbrowser") {
|
|
31923
32463
|
usesBrowserAuth = true;
|
|
@@ -31997,4 +32537,4 @@ async function runDevServer(projectDir, options) {
|
|
|
31997
32537
|
export {
|
|
31998
32538
|
runDevServer
|
|
31999
32539
|
};
|
|
32000
|
-
//# sourceMappingURL=dev-
|
|
32540
|
+
//# sourceMappingURL=dev-PHFYLJPJ.js.map
|