yamchart 0.7.2 → 0.8.1
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/CLAUDE.md +51 -0
- package/dist/{advisor-5BTRAICH.js → advisor-57BOMUUF.js} +11 -10
- package/dist/{advisor-5BTRAICH.js.map → advisor-57BOMUUF.js.map} +1 -1
- package/dist/{chunk-5VA43CTW.js → chunk-E2UZXDF6.js} +37 -42
- package/dist/chunk-E2UZXDF6.js.map +1 -0
- package/dist/{chunk-SSUVEADJ.js → chunk-JYQKDWLG.js} +76 -14
- package/dist/chunk-JYQKDWLG.js.map +1 -0
- package/dist/{chunk-565OEBW7.js → chunk-OTAUP5RC.js} +3 -3
- package/dist/{chunk-F6QAWPYU.js → chunk-TZKNU5TD.js} +2 -2
- package/dist/chunk-TZKNU5TD.js.map +1 -0
- package/dist/chunk-UND73EOB.js +449 -0
- package/dist/chunk-UND73EOB.js.map +1 -0
- package/dist/{chunk-UDRJFEJU.js → chunk-X6LQGWUX.js} +349 -176
- package/dist/chunk-X6LQGWUX.js.map +1 -0
- package/dist/{connection-utils-AGSQ5HNN.js → connection-utils-FTSZU2VF.js} +5 -4
- package/dist/{describe-AA4UT4U6.js → describe-TGIOBNJB.js} +5 -4
- package/dist/{describe-AA4UT4U6.js.map → describe-TGIOBNJB.js.map} +1 -1
- package/dist/{dev-EUWIRL4N.js → dev-MRZ76V74.js} +485 -44
- package/dist/dev-MRZ76V74.js.map +1 -0
- package/dist/{dist-LZNDQDH3.js → dist-PVHFUYP2.js} +23 -3
- package/dist/{dist-YTGUIBKG.js → dist-WDTDQDTX.js} +72 -1
- package/dist/dist-WDTDQDTX.js.map +1 -0
- package/dist/index.js +136 -18
- package/dist/index.js.map +1 -1
- package/dist/{init-CI4VARQG.js → init-UYQE5DPU.js} +2 -2
- package/dist/{init-CI4VARQG.js.map → init-UYQE5DPU.js.map} +1 -1
- package/dist/public/assets/EventManagement-BlxJ2TFw.js +18 -0
- package/dist/public/assets/{LoginPage-BwMmpq8e.js → LoginPage-BT8ikmPn.js} +1 -1
- package/dist/public/assets/PublicViewer-B4uFxgbt.js +1 -0
- package/dist/public/assets/{SetupWizard-DU8R2dPp.js → SetupWizard-njrOhCzw.js} +1 -1
- package/dist/public/assets/ShareManagement-Bg16oJhW.js +1 -0
- package/dist/public/assets/UserManagement-tiCIT4UY.js +1 -0
- package/dist/public/assets/index-B41yj3io.js +187 -0
- package/dist/public/assets/{index-C0hWblBI.css → index-BZ25r23j.css} +1 -1
- package/dist/public/assets/{index.es-YeJujQ5o.js → index.es-BuktD_R2.js} +1 -1
- package/dist/public/assets/{jspdf.es.min-BzZgn3ka.js → jspdf.es.min-DFRl2hZQ.js} +3 -3
- package/dist/public/index.html +2 -2
- package/dist/{query-THDVBBVZ.js → query-JRMMNXX6.js} +5 -4
- package/dist/{query-THDVBBVZ.js.map → query-JRMMNXX6.js.map} +1 -1
- package/dist/{sample-6WPQS7PA.js → sample-VGIY4U4J.js} +5 -4
- package/dist/{sample-6WPQS7PA.js.map → sample-VGIY4U4J.js.map} +1 -1
- package/dist/{search-657DXBRS.js → search-QSYNG4SR.js} +5 -4
- package/dist/{search-657DXBRS.js.map → search-QSYNG4SR.js.map} +1 -1
- package/dist/semantic-RAP3S5PQ.js +39 -0
- package/dist/semantic-RAP3S5PQ.js.map +1 -0
- package/dist/{sync-warehouse-4KBV5S3L.js → sync-warehouse-XHTBZH25.js} +5 -4
- package/dist/{sync-warehouse-4KBV5S3L.js.map → sync-warehouse-XHTBZH25.js.map} +1 -1
- package/dist/{tables-KLDBUUSE.js → tables-UOO342TA.js} +5 -4
- package/dist/{tables-KLDBUUSE.js.map → tables-UOO342TA.js.map} +1 -1
- package/dist/templates/default/.claude/skills/databricks/SKILL.md +76 -0
- package/dist/templates/default/.claude/skills/dbt-advisor/SKILL.md +107 -0
- package/dist/templates/default/.claude/skills/duckdb/SKILL.md +67 -0
- package/dist/templates/default/.claude/skills/mysql/SKILL.md +69 -0
- package/dist/templates/default/.claude/skills/postgres/SKILL.md +69 -0
- package/dist/templates/default/.claude/skills/snowflake/SKILL.md +64 -0
- package/dist/templates/default/CLAUDE.md +8 -1
- package/dist/templates/default/docs/yamchart-reference.md +222 -1
- package/dist/{test-JSAWS5ZP.js → test-TXRZWNXK.js} +5 -4
- package/dist/{test-JSAWS5ZP.js.map → test-TXRZWNXK.js.map} +1 -1
- package/dist/update-WMATDZTO.js +220 -0
- package/dist/update-WMATDZTO.js.map +1 -0
- package/package.json +8 -6
- package/dist/chunk-5VA43CTW.js.map +0 -1
- package/dist/chunk-F6QAWPYU.js.map +0 -1
- package/dist/chunk-SSUVEADJ.js.map +0 -1
- package/dist/chunk-UDRJFEJU.js.map +0 -1
- package/dist/dev-EUWIRL4N.js.map +0 -1
- package/dist/dist-YTGUIBKG.js.map +0 -1
- package/dist/public/assets/PublicViewer-vIDojjIR.js +0 -1
- package/dist/public/assets/ShareManagement-7iS6lM2T.js +0 -1
- package/dist/public/assets/UserManagement-By7YRZrF.js +0 -1
- package/dist/public/assets/index-CXx1PiRF.js +0 -174
- package/dist/update-HCR6MYJX.js +0 -88
- package/dist/update-HCR6MYJX.js.map +0 -1
- /package/dist/{chunk-565OEBW7.js.map → chunk-OTAUP5RC.js.map} +0 -0
- /package/dist/{connection-utils-AGSQ5HNN.js.map → connection-utils-FTSZU2VF.js.map} +0 -0
- /package/dist/{dist-LZNDQDH3.js.map → dist-PVHFUYP2.js.map} +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
loadEnvFile,
|
|
3
3
|
validateProject
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-E2UZXDF6.js";
|
|
5
5
|
import {
|
|
6
6
|
box,
|
|
7
7
|
detail,
|
|
@@ -16,9 +16,19 @@ import {
|
|
|
16
16
|
ChartSchema,
|
|
17
17
|
ConnectionSchema,
|
|
18
18
|
DashboardSchema,
|
|
19
|
+
EventSchema,
|
|
20
|
+
EventsFileSchema,
|
|
19
21
|
ProjectSchema,
|
|
20
|
-
ScheduleSchema
|
|
21
|
-
|
|
22
|
+
ScheduleSchema,
|
|
23
|
+
SemanticQuerySchema
|
|
24
|
+
} from "./chunk-X6LQGWUX.js";
|
|
25
|
+
import {
|
|
26
|
+
AuthDatabase,
|
|
27
|
+
generateSessionToken,
|
|
28
|
+
hashPassword,
|
|
29
|
+
parseTtl,
|
|
30
|
+
verifyPassword
|
|
31
|
+
} from "./chunk-4P5UHWYK.js";
|
|
22
32
|
import {
|
|
23
33
|
DuckDBConnector,
|
|
24
34
|
MySQLConnector,
|
|
@@ -40,14 +50,13 @@ import {
|
|
|
40
50
|
resolveMySQLAuth,
|
|
41
51
|
resolvePostgresAuth,
|
|
42
52
|
resolveSnowflakeAuth
|
|
43
|
-
} from "./chunk-
|
|
53
|
+
} from "./chunk-JYQKDWLG.js";
|
|
44
54
|
import {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
} from "./chunk-4P5UHWYK.js";
|
|
55
|
+
SemanticModelBuilder,
|
|
56
|
+
SemanticQueryCompiler,
|
|
57
|
+
SemanticValidationError,
|
|
58
|
+
validateSemanticQuery
|
|
59
|
+
} from "./chunk-UND73EOB.js";
|
|
51
60
|
import "./chunk-DGUM43GV.js";
|
|
52
61
|
|
|
53
62
|
// src/commands/dev.ts
|
|
@@ -76,6 +85,8 @@ var ConfigLoader = class {
|
|
|
76
85
|
dashboardFolders = /* @__PURE__ */ new Map();
|
|
77
86
|
dashboardFilePaths = /* @__PURE__ */ new Map();
|
|
78
87
|
schedules = /* @__PURE__ */ new Map();
|
|
88
|
+
events = [];
|
|
89
|
+
validationErrors = /* @__PURE__ */ new Map();
|
|
79
90
|
watcher = null;
|
|
80
91
|
onChangeCallbacks = [];
|
|
81
92
|
constructor(projectDir) {
|
|
@@ -91,12 +102,15 @@ var ConfigLoader = class {
|
|
|
91
102
|
this.dashboardFolders.clear();
|
|
92
103
|
this.dashboardFilePaths.clear();
|
|
93
104
|
this.schedules.clear();
|
|
105
|
+
this.events = [];
|
|
106
|
+
this.validationErrors.clear();
|
|
94
107
|
await this.loadProject();
|
|
95
108
|
await this.loadConnections();
|
|
96
109
|
await this.loadCharts();
|
|
97
110
|
await this.loadModels();
|
|
98
111
|
await this.loadDashboards();
|
|
99
112
|
await this.loadSchedules();
|
|
113
|
+
await this.loadEvents();
|
|
100
114
|
}
|
|
101
115
|
async loadProject() {
|
|
102
116
|
const projectPath = join(this.projectDir, "yamchart.yaml");
|
|
@@ -165,7 +179,16 @@ var ConfigLoader = class {
|
|
|
165
179
|
const relDir = relative(baseDir, dirname(fullPath)).replace(/\\/g, "/");
|
|
166
180
|
this.chartFolders.set(name, relDir || "");
|
|
167
181
|
} else {
|
|
168
|
-
|
|
182
|
+
const relPath = relative(baseDir, fullPath);
|
|
183
|
+
const name = parsed && typeof parsed === "object" && "name" in parsed && typeof parsed.name === "string" ? parsed.name : void 0;
|
|
184
|
+
const errorMsg = result.error.errors.map((e) => {
|
|
185
|
+
const path = e.path.length > 0 ? e.path.join(".") : "(root)";
|
|
186
|
+
return `${path}: ${e.message}`;
|
|
187
|
+
}).join("; ");
|
|
188
|
+
console.warn(`Invalid chart file ${relPath}: ${errorMsg}`);
|
|
189
|
+
if (name) {
|
|
190
|
+
this.validationErrors.set(name, { file: relPath, message: errorMsg });
|
|
191
|
+
}
|
|
169
192
|
}
|
|
170
193
|
}
|
|
171
194
|
}
|
|
@@ -230,7 +253,16 @@ var ConfigLoader = class {
|
|
|
230
253
|
const relDir = relative(baseDir, dirname(fullPath)).replace(/\\/g, "/");
|
|
231
254
|
this.dashboardFolders.set(name, relDir || "");
|
|
232
255
|
} else {
|
|
233
|
-
|
|
256
|
+
const relPath = relative(baseDir, fullPath);
|
|
257
|
+
const name = parsed && typeof parsed === "object" && "name" in parsed && typeof parsed.name === "string" ? parsed.name : void 0;
|
|
258
|
+
const errorMsg = result.error.errors.map((e) => {
|
|
259
|
+
const path = e.path.length > 0 ? e.path.join(".") : "(root)";
|
|
260
|
+
return `${path}: ${e.message}`;
|
|
261
|
+
}).join("; ");
|
|
262
|
+
console.warn(`Invalid dashboard file ${relPath}: ${errorMsg}`);
|
|
263
|
+
if (name) {
|
|
264
|
+
this.validationErrors.set(name, { file: relPath, message: errorMsg });
|
|
265
|
+
}
|
|
234
266
|
}
|
|
235
267
|
}
|
|
236
268
|
}
|
|
@@ -257,6 +289,22 @@ var ConfigLoader = class {
|
|
|
257
289
|
}
|
|
258
290
|
}
|
|
259
291
|
}
|
|
292
|
+
async loadEvents() {
|
|
293
|
+
const eventsPath = join(this.projectDir, "events.yaml");
|
|
294
|
+
try {
|
|
295
|
+
await access(eventsPath);
|
|
296
|
+
} catch {
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
const content = await readFile(eventsPath, "utf-8");
|
|
300
|
+
const parsed = parseYaml(content);
|
|
301
|
+
const result = EventsFileSchema.safeParse(parsed);
|
|
302
|
+
if (result.success) {
|
|
303
|
+
this.events = result.data.events;
|
|
304
|
+
} else {
|
|
305
|
+
console.warn(`Invalid events.yaml: ${result.error.message}`);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
260
308
|
startWatching() {
|
|
261
309
|
if (this.watcher)
|
|
262
310
|
return;
|
|
@@ -266,7 +314,8 @@ var ConfigLoader = class {
|
|
|
266
314
|
join(this.projectDir, "charts"),
|
|
267
315
|
join(this.projectDir, "models"),
|
|
268
316
|
join(this.projectDir, "dashboards"),
|
|
269
|
-
join(this.projectDir, "schedules")
|
|
317
|
+
join(this.projectDir, "schedules"),
|
|
318
|
+
join(this.projectDir, "events.yaml")
|
|
270
319
|
];
|
|
271
320
|
this.watcher = watch(watchPaths, {
|
|
272
321
|
ignored: /(^|[\/\\])\../,
|
|
@@ -288,6 +337,17 @@ var ConfigLoader = class {
|
|
|
288
337
|
console.log(`File removed: ${path}`);
|
|
289
338
|
await this.reload();
|
|
290
339
|
});
|
|
340
|
+
this.watcher.on("error", (err) => {
|
|
341
|
+
const code = err.code;
|
|
342
|
+
if (code === "EMFILE") {
|
|
343
|
+
console.warn(`Warning: Too many open files for file watching. The server will continue running but won't auto-reload on file changes.
|
|
344
|
+
Fix: run "ulimit -n 10240" before starting yamchart dev, or add it to your shell profile.`);
|
|
345
|
+
this.watcher?.close();
|
|
346
|
+
this.watcher = null;
|
|
347
|
+
} else {
|
|
348
|
+
console.error("File watcher error:", err);
|
|
349
|
+
}
|
|
350
|
+
});
|
|
291
351
|
}
|
|
292
352
|
async reload() {
|
|
293
353
|
try {
|
|
@@ -364,6 +424,18 @@ var ConfigLoader = class {
|
|
|
364
424
|
getSchedules() {
|
|
365
425
|
return Array.from(this.schedules.values());
|
|
366
426
|
}
|
|
427
|
+
getEvents() {
|
|
428
|
+
return this.events;
|
|
429
|
+
}
|
|
430
|
+
getProjectDir() {
|
|
431
|
+
return this.projectDir;
|
|
432
|
+
}
|
|
433
|
+
getValidationError(name) {
|
|
434
|
+
return this.validationErrors.get(name);
|
|
435
|
+
}
|
|
436
|
+
getValidationErrors() {
|
|
437
|
+
return this.validationErrors;
|
|
438
|
+
}
|
|
367
439
|
};
|
|
368
440
|
|
|
369
441
|
// ../server/dist/services/cache.js
|
|
@@ -507,6 +579,24 @@ var QueryService = class {
|
|
|
507
579
|
getCacheStats() {
|
|
508
580
|
return this.cache.getStats();
|
|
509
581
|
}
|
|
582
|
+
async executeRawSql(sql) {
|
|
583
|
+
return this.connector.execute(sql);
|
|
584
|
+
}
|
|
585
|
+
async executeModel(modelName, params, userContext, limit = 1e3, filters) {
|
|
586
|
+
const model = this.models.get(modelName);
|
|
587
|
+
if (!model) {
|
|
588
|
+
throw new Error(`Unknown model: ${modelName}`);
|
|
589
|
+
}
|
|
590
|
+
const context = createTemplateContext(params, this.refs, userContext);
|
|
591
|
+
let sql = renderTemplate(model.sql, context);
|
|
592
|
+
if (filters && Object.keys(filters).length > 0) {
|
|
593
|
+
const clauses = Object.entries(filters).map(([field, value]) => `${field} = '${value.replace(/'/g, "''")}'`);
|
|
594
|
+
sql = `SELECT * FROM (${sql}) _detail WHERE ${clauses.join(" AND ")} LIMIT ${limit}`;
|
|
595
|
+
} else {
|
|
596
|
+
sql = `SELECT * FROM (${sql}) _detail LIMIT ${limit}`;
|
|
597
|
+
}
|
|
598
|
+
return this.connector.execute(sql);
|
|
599
|
+
}
|
|
510
600
|
async executeParameterOptions(modelName, valueField, labelField) {
|
|
511
601
|
const model = this.models.get(modelName);
|
|
512
602
|
if (!model) {
|
|
@@ -778,13 +868,30 @@ function buildComparisonResponse(input) {
|
|
|
778
868
|
previousPeriodLabel: formatPeriodLabel(previousStart, previousEnd)
|
|
779
869
|
};
|
|
780
870
|
}
|
|
871
|
+
function chartNotFoundResponse(configLoader, name) {
|
|
872
|
+
const validationError = configLoader.getValidationError(name);
|
|
873
|
+
if (validationError) {
|
|
874
|
+
return {
|
|
875
|
+
status: 422,
|
|
876
|
+
body: {
|
|
877
|
+
error: `Invalid chart config "${name}" (${validationError.file}): ${validationError.message}`,
|
|
878
|
+
errorType: "validation_error"
|
|
879
|
+
}
|
|
880
|
+
};
|
|
881
|
+
}
|
|
882
|
+
return {
|
|
883
|
+
status: 404,
|
|
884
|
+
body: { error: `Chart not found: ${name}`, errorType: "not_found" }
|
|
885
|
+
};
|
|
886
|
+
}
|
|
781
887
|
async function chartRoutes(fastify, options) {
|
|
782
888
|
const { configLoader, queryService } = options;
|
|
783
889
|
fastify.get("/api/charts/:name", async (request, reply) => {
|
|
784
890
|
const { name } = request.params;
|
|
785
891
|
const chart = configLoader.getChartByName(name);
|
|
786
892
|
if (!chart) {
|
|
787
|
-
|
|
893
|
+
const resp = chartNotFoundResponse(configLoader, name);
|
|
894
|
+
return reply.status(resp.status).send(resp.body);
|
|
788
895
|
}
|
|
789
896
|
return {
|
|
790
897
|
name: chart.name,
|
|
@@ -801,7 +908,8 @@ async function chartRoutes(fastify, options) {
|
|
|
801
908
|
const includeConfig = request.query.includeConfig === "true";
|
|
802
909
|
const chart = configLoader.getChartByName(name);
|
|
803
910
|
if (!chart) {
|
|
804
|
-
|
|
911
|
+
const resp = chartNotFoundResponse(configLoader, name);
|
|
912
|
+
return reply.status(resp.status).send(resp.body);
|
|
805
913
|
}
|
|
806
914
|
try {
|
|
807
915
|
const result = await queryService.executeChart(chart, params, getUserContext(request));
|
|
@@ -867,18 +975,70 @@ async function chartRoutes(fastify, options) {
|
|
|
867
975
|
title: chart.title,
|
|
868
976
|
description: chart.description,
|
|
869
977
|
parameters: chart.parameters ?? [],
|
|
870
|
-
chart: chart.chart
|
|
978
|
+
chart: chart.chart,
|
|
979
|
+
drillDown: chart.drillDown
|
|
871
980
|
};
|
|
872
981
|
}
|
|
873
982
|
if (comparison) {
|
|
874
983
|
response.comparison = comparison;
|
|
875
984
|
}
|
|
985
|
+
let goalData = void 0;
|
|
986
|
+
const goals = chart.chart?.goals;
|
|
987
|
+
const modelGoal = goals?.find((g) => g.type === "model" && g.source?.model);
|
|
988
|
+
if (modelGoal?.source) {
|
|
989
|
+
try {
|
|
990
|
+
const goalResult = await queryService.executeModel(modelGoal.source.model, params, getUserContext(request));
|
|
991
|
+
goalData = { rows: goalResult.rows };
|
|
992
|
+
} catch {
|
|
993
|
+
}
|
|
994
|
+
}
|
|
995
|
+
if (goalData) {
|
|
996
|
+
response.goalData = goalData;
|
|
997
|
+
}
|
|
876
998
|
return response;
|
|
877
999
|
} catch (error2) {
|
|
878
1000
|
const message = error2 instanceof Error ? error2.message : "Query execution failed";
|
|
879
1001
|
return reply.status(500).send({ error: message, errorType: classifyError(error2) });
|
|
880
1002
|
}
|
|
881
1003
|
});
|
|
1004
|
+
fastify.post("/api/charts/:name/detail", async (request, reply) => {
|
|
1005
|
+
const { name } = request.params;
|
|
1006
|
+
const params = request.body ?? {};
|
|
1007
|
+
const chart = configLoader.getChartByName(name);
|
|
1008
|
+
if (!chart) {
|
|
1009
|
+
const resp = chartNotFoundResponse(configLoader, name);
|
|
1010
|
+
return reply.status(resp.status).send(resp.body);
|
|
1011
|
+
}
|
|
1012
|
+
const detailModel = chart.drillDown?.model;
|
|
1013
|
+
if (!detailModel) {
|
|
1014
|
+
return reply.status(400).send({
|
|
1015
|
+
error: "Chart has no detail model configured",
|
|
1016
|
+
errorType: "validation_error"
|
|
1017
|
+
});
|
|
1018
|
+
}
|
|
1019
|
+
try {
|
|
1020
|
+
const dd = chart.drillDown;
|
|
1021
|
+
const drillFields = dd.fields ? typeof dd.fields === "string" ? [dd.fields] : dd.fields : dd.field ? [dd.field] : [];
|
|
1022
|
+
const drillFilters = {};
|
|
1023
|
+
for (const f of drillFields) {
|
|
1024
|
+
if (params[f] != null && params[f] !== "") {
|
|
1025
|
+
drillFilters[f] = String(params[f]);
|
|
1026
|
+
}
|
|
1027
|
+
}
|
|
1028
|
+
const result = await queryService.executeModel(detailModel, params, getUserContext(request), 1e3, Object.keys(drillFilters).length > 0 ? drillFilters : void 0);
|
|
1029
|
+
return {
|
|
1030
|
+
columns: result.columns,
|
|
1031
|
+
rows: result.rows,
|
|
1032
|
+
meta: {
|
|
1033
|
+
rowCount: result.rowCount,
|
|
1034
|
+
durationMs: result.durationMs
|
|
1035
|
+
}
|
|
1036
|
+
};
|
|
1037
|
+
} catch (error2) {
|
|
1038
|
+
const message = error2 instanceof Error ? error2.message : "Detail query failed";
|
|
1039
|
+
return reply.status(500).send({ error: message, errorType: classifyError(error2) });
|
|
1040
|
+
}
|
|
1041
|
+
});
|
|
882
1042
|
fastify.post("/api/charts/batch", async (request, reply) => {
|
|
883
1043
|
const { charts, includeConfig } = request.body;
|
|
884
1044
|
if (!charts || !Array.isArray(charts)) {
|
|
@@ -887,7 +1047,8 @@ async function chartRoutes(fastify, options) {
|
|
|
887
1047
|
const results = await Promise.all(charts.map(async ({ name, params = {} }) => {
|
|
888
1048
|
const chart = configLoader.getChartByName(name);
|
|
889
1049
|
if (!chart) {
|
|
890
|
-
|
|
1050
|
+
const resp = chartNotFoundResponse(configLoader, name);
|
|
1051
|
+
return { name, ...resp.body };
|
|
891
1052
|
}
|
|
892
1053
|
try {
|
|
893
1054
|
const result = await queryService.executeChart(chart, params, getUserContext(request));
|
|
@@ -908,9 +1069,19 @@ async function chartRoutes(fastify, options) {
|
|
|
908
1069
|
title: chart.title,
|
|
909
1070
|
description: chart.description,
|
|
910
1071
|
parameters: chart.parameters ?? [],
|
|
911
|
-
chart: chart.chart
|
|
1072
|
+
chart: chart.chart,
|
|
1073
|
+
drillDown: chart.drillDown
|
|
912
1074
|
};
|
|
913
1075
|
}
|
|
1076
|
+
const goals = chart.chart?.goals;
|
|
1077
|
+
const modelGoal = goals?.find((g) => g.type === "model" && g.source?.model);
|
|
1078
|
+
if (modelGoal?.source) {
|
|
1079
|
+
try {
|
|
1080
|
+
const goalResult = await queryService.executeModel(modelGoal.source.model, params, getUserContext(request));
|
|
1081
|
+
response.goalData = { rows: goalResult.rows };
|
|
1082
|
+
} catch {
|
|
1083
|
+
}
|
|
1084
|
+
}
|
|
914
1085
|
return response;
|
|
915
1086
|
} catch (error2) {
|
|
916
1087
|
const message = error2 instanceof Error ? error2.message : "Query failed";
|
|
@@ -923,7 +1094,8 @@ async function chartRoutes(fastify, options) {
|
|
|
923
1094
|
const { name } = request.params;
|
|
924
1095
|
const chart = configLoader.getChartByName(name);
|
|
925
1096
|
if (!chart) {
|
|
926
|
-
|
|
1097
|
+
const resp = chartNotFoundResponse(configLoader, name);
|
|
1098
|
+
return reply.status(resp.status).send(resp.body);
|
|
927
1099
|
}
|
|
928
1100
|
queryService.invalidateChart(name);
|
|
929
1101
|
return { success: true, message: `Cache invalidated for chart: ${name}` };
|
|
@@ -932,7 +1104,8 @@ async function chartRoutes(fastify, options) {
|
|
|
932
1104
|
const { chartName, paramName } = request.params;
|
|
933
1105
|
const chart = configLoader.getChartByName(chartName);
|
|
934
1106
|
if (!chart) {
|
|
935
|
-
|
|
1107
|
+
const resp = chartNotFoundResponse(configLoader, chartName);
|
|
1108
|
+
return reply.status(resp.status).send(resp.body);
|
|
936
1109
|
}
|
|
937
1110
|
const param = chart.parameters?.find((p) => p.name === paramName);
|
|
938
1111
|
if (!param) {
|
|
@@ -954,10 +1127,73 @@ async function chartRoutes(fastify, options) {
|
|
|
954
1127
|
});
|
|
955
1128
|
}
|
|
956
1129
|
|
|
957
|
-
// ../server/dist/routes/
|
|
958
|
-
import { writeFile } from "fs/promises";
|
|
1130
|
+
// ../server/dist/routes/chart-events.js
|
|
1131
|
+
import { readFile as readFile2, writeFile } from "fs/promises";
|
|
959
1132
|
import { join as join3 } from "path";
|
|
960
|
-
import { stringify as stringifyYaml } from "yaml";
|
|
1133
|
+
import { parse as parseYaml2, stringify as stringifyYaml } from "yaml";
|
|
1134
|
+
async function readEventsFile(projectDir) {
|
|
1135
|
+
const eventsPath = join3(projectDir, "events.yaml");
|
|
1136
|
+
try {
|
|
1137
|
+
const content = await readFile2(eventsPath, "utf-8");
|
|
1138
|
+
const parsed = parseYaml2(content);
|
|
1139
|
+
const result = EventsFileSchema.safeParse(parsed);
|
|
1140
|
+
return result.success ? result.data.events : [];
|
|
1141
|
+
} catch {
|
|
1142
|
+
return [];
|
|
1143
|
+
}
|
|
1144
|
+
}
|
|
1145
|
+
async function writeEventsFile(projectDir, events) {
|
|
1146
|
+
const eventsPath = join3(projectDir, "events.yaml");
|
|
1147
|
+
await writeFile(eventsPath, stringifyYaml({ events }));
|
|
1148
|
+
}
|
|
1149
|
+
async function chartEventsRoutes(fastify, options) {
|
|
1150
|
+
const { configLoader, projectDir } = options;
|
|
1151
|
+
fastify.get("/api/chart-events", async () => {
|
|
1152
|
+
return { events: configLoader.getEvents() };
|
|
1153
|
+
});
|
|
1154
|
+
fastify.post("/api/chart-events", async (request, reply) => {
|
|
1155
|
+
const result = EventSchema.safeParse(request.body);
|
|
1156
|
+
if (!result.success) {
|
|
1157
|
+
return reply.status(400).send({ error: result.error.message });
|
|
1158
|
+
}
|
|
1159
|
+
const events = await readEventsFile(projectDir);
|
|
1160
|
+
events.push(result.data);
|
|
1161
|
+
await writeEventsFile(projectDir, events);
|
|
1162
|
+
await configLoader.load();
|
|
1163
|
+
return reply.status(201).send({ event: result.data, index: events.length - 1 });
|
|
1164
|
+
});
|
|
1165
|
+
fastify.delete("/api/chart-events/:index", async (request, reply) => {
|
|
1166
|
+
const index = parseInt(request.params.index, 10);
|
|
1167
|
+
const events = await readEventsFile(projectDir);
|
|
1168
|
+
if (isNaN(index) || index < 0 || index >= events.length) {
|
|
1169
|
+
return reply.status(404).send({ error: "Event not found" });
|
|
1170
|
+
}
|
|
1171
|
+
events.splice(index, 1);
|
|
1172
|
+
await writeEventsFile(projectDir, events);
|
|
1173
|
+
await configLoader.load();
|
|
1174
|
+
return { success: true };
|
|
1175
|
+
});
|
|
1176
|
+
fastify.put("/api/chart-events/:index", async (request, reply) => {
|
|
1177
|
+
const index = parseInt(request.params.index, 10);
|
|
1178
|
+
const events = await readEventsFile(projectDir);
|
|
1179
|
+
if (isNaN(index) || index < 0 || index >= events.length) {
|
|
1180
|
+
return reply.status(404).send({ error: "Event not found" });
|
|
1181
|
+
}
|
|
1182
|
+
const result = EventSchema.safeParse(request.body);
|
|
1183
|
+
if (!result.success) {
|
|
1184
|
+
return reply.status(400).send({ error: result.error.message });
|
|
1185
|
+
}
|
|
1186
|
+
events[index] = result.data;
|
|
1187
|
+
await writeEventsFile(projectDir, events);
|
|
1188
|
+
await configLoader.load();
|
|
1189
|
+
return { event: result.data };
|
|
1190
|
+
});
|
|
1191
|
+
}
|
|
1192
|
+
|
|
1193
|
+
// ../server/dist/routes/dashboards.js
|
|
1194
|
+
import { writeFile as writeFile2 } from "fs/promises";
|
|
1195
|
+
import { join as join4 } from "path";
|
|
1196
|
+
import { stringify as stringifyYaml2 } from "yaml";
|
|
961
1197
|
function classifyError2(error2) {
|
|
962
1198
|
const msg = error2 instanceof Error ? error2.message : String(error2);
|
|
963
1199
|
const lower = msg.toLowerCase();
|
|
@@ -969,6 +1205,22 @@ function classifyError2(error2) {
|
|
|
969
1205
|
}
|
|
970
1206
|
return "query_error";
|
|
971
1207
|
}
|
|
1208
|
+
function dashboardNotFoundResponse(configLoader, name) {
|
|
1209
|
+
const validationError = configLoader.getValidationError(name);
|
|
1210
|
+
if (validationError) {
|
|
1211
|
+
return {
|
|
1212
|
+
status: 422,
|
|
1213
|
+
body: {
|
|
1214
|
+
error: `Invalid dashboard config "${name}" (${validationError.file}): ${validationError.message}`,
|
|
1215
|
+
errorType: "validation_error"
|
|
1216
|
+
}
|
|
1217
|
+
};
|
|
1218
|
+
}
|
|
1219
|
+
return {
|
|
1220
|
+
status: 404,
|
|
1221
|
+
body: { error: `Dashboard not found: ${name}`, errorType: "not_found" }
|
|
1222
|
+
};
|
|
1223
|
+
}
|
|
972
1224
|
async function dashboardRoutes(fastify, options) {
|
|
973
1225
|
const { configLoader, gitService, projectDir } = options;
|
|
974
1226
|
fastify.get("/api/dashboards", async () => {
|
|
@@ -984,7 +1236,8 @@ async function dashboardRoutes(fastify, options) {
|
|
|
984
1236
|
const { id } = request.params;
|
|
985
1237
|
const dashboard = configLoader.getDashboardByName(id);
|
|
986
1238
|
if (!dashboard) {
|
|
987
|
-
|
|
1239
|
+
const resp = dashboardNotFoundResponse(configLoader, id);
|
|
1240
|
+
return reply.status(resp.status).send(resp.body);
|
|
988
1241
|
}
|
|
989
1242
|
const currentBranch = await gitService.getCurrentBranch();
|
|
990
1243
|
return {
|
|
@@ -1018,8 +1271,8 @@ async function dashboardRoutes(fastify, options) {
|
|
|
1018
1271
|
}
|
|
1019
1272
|
updated = { ...dashboard, layout };
|
|
1020
1273
|
}
|
|
1021
|
-
const filePath = configLoader.getDashboardFilePath(id) ??
|
|
1022
|
-
await
|
|
1274
|
+
const filePath = configLoader.getDashboardFilePath(id) ?? join4(projectDir, "dashboards", `${id}.yaml`);
|
|
1275
|
+
await writeFile2(filePath, stringifyYaml2(updated));
|
|
1023
1276
|
await configLoader.load();
|
|
1024
1277
|
const commitMessage = message || `Update ${dashboard.title} layout`;
|
|
1025
1278
|
const result = await gitService.commitAndPush(commitMessage);
|
|
@@ -1066,6 +1319,10 @@ async function dashboardRoutes(fastify, options) {
|
|
|
1066
1319
|
const results = await Promise.all(chartRefs.map(async (chartRef) => {
|
|
1067
1320
|
const chart = configLoader.getChartByName(chartRef);
|
|
1068
1321
|
if (!chart) {
|
|
1322
|
+
const validationError = configLoader.getValidationError(chartRef);
|
|
1323
|
+
if (validationError) {
|
|
1324
|
+
return { chart: chartRef, error: `Invalid chart config: ${validationError.message}`, errorType: "validation_error" };
|
|
1325
|
+
}
|
|
1069
1326
|
return { chart: chartRef, error: "Chart not found", errorType: "not_found" };
|
|
1070
1327
|
}
|
|
1071
1328
|
try {
|
|
@@ -1132,6 +1389,143 @@ async function dashboardRoutes(fastify, options) {
|
|
|
1132
1389
|
});
|
|
1133
1390
|
}
|
|
1134
1391
|
|
|
1392
|
+
// ../server/dist/routes/semantic.js
|
|
1393
|
+
async function semanticRoutes(fastify, options) {
|
|
1394
|
+
const { semanticService, queryService, projectDir } = options;
|
|
1395
|
+
fastify.get("/api/semantic/meta", async (_request2, reply) => {
|
|
1396
|
+
const model = semanticService.getModel();
|
|
1397
|
+
if (!model) {
|
|
1398
|
+
return reply.status(503).send({
|
|
1399
|
+
error: "Semantic model not built. Ensure catalog.json exists or models are loaded.",
|
|
1400
|
+
errorType: "not_ready"
|
|
1401
|
+
});
|
|
1402
|
+
}
|
|
1403
|
+
return model;
|
|
1404
|
+
});
|
|
1405
|
+
fastify.post("/api/semantic/query", async (request, reply) => {
|
|
1406
|
+
const parseResult = SemanticQuerySchema.safeParse(request.body);
|
|
1407
|
+
if (!parseResult.success) {
|
|
1408
|
+
return reply.status(400).send({
|
|
1409
|
+
error: parseResult.error.issues.map((i) => i.message).join("; "),
|
|
1410
|
+
errorType: "validation_error"
|
|
1411
|
+
});
|
|
1412
|
+
}
|
|
1413
|
+
const query = parseResult.data;
|
|
1414
|
+
try {
|
|
1415
|
+
const result = semanticService.compileQuery(query);
|
|
1416
|
+
if (queryService && typeof queryService.executeRawSql === "function") {
|
|
1417
|
+
try {
|
|
1418
|
+
const startTime = Date.now();
|
|
1419
|
+
const queryResult = await queryService.executeRawSql(result.sql);
|
|
1420
|
+
const duration = Date.now() - startTime;
|
|
1421
|
+
return {
|
|
1422
|
+
columns: queryResult.columns,
|
|
1423
|
+
rows: queryResult.rows,
|
|
1424
|
+
meta: {
|
|
1425
|
+
compiled_sql: result.sql,
|
|
1426
|
+
duration_ms: duration,
|
|
1427
|
+
tier: result.tier,
|
|
1428
|
+
entity: result.entity,
|
|
1429
|
+
row_count: queryResult.rows.length
|
|
1430
|
+
}
|
|
1431
|
+
};
|
|
1432
|
+
} catch (execError) {
|
|
1433
|
+
const message = execError instanceof Error ? execError.message : String(execError);
|
|
1434
|
+
return reply.status(500).send({
|
|
1435
|
+
error: message,
|
|
1436
|
+
errorType: "query_error",
|
|
1437
|
+
compiled_sql: result.sql
|
|
1438
|
+
});
|
|
1439
|
+
}
|
|
1440
|
+
}
|
|
1441
|
+
return {
|
|
1442
|
+
columns: [],
|
|
1443
|
+
rows: [],
|
|
1444
|
+
meta: {
|
|
1445
|
+
compiled_sql: result.sql,
|
|
1446
|
+
tier: result.tier,
|
|
1447
|
+
entity: result.entity
|
|
1448
|
+
}
|
|
1449
|
+
};
|
|
1450
|
+
} catch (error2) {
|
|
1451
|
+
if (error2 instanceof SemanticValidationError) {
|
|
1452
|
+
return reply.status(400).send({
|
|
1453
|
+
error: error2.message,
|
|
1454
|
+
errorType: "validation_error",
|
|
1455
|
+
code: error2.code,
|
|
1456
|
+
available: error2.available
|
|
1457
|
+
});
|
|
1458
|
+
}
|
|
1459
|
+
throw error2;
|
|
1460
|
+
}
|
|
1461
|
+
});
|
|
1462
|
+
fastify.post("/api/semantic/save-model", async (request, reply) => {
|
|
1463
|
+
const { name, sql, path: filePath } = request.body;
|
|
1464
|
+
if (!name || !sql || !filePath) {
|
|
1465
|
+
return reply.status(400).send({
|
|
1466
|
+
error: "name, sql, and path are required",
|
|
1467
|
+
errorType: "validation_error"
|
|
1468
|
+
});
|
|
1469
|
+
}
|
|
1470
|
+
const { writeFileSync, mkdirSync } = await import("fs");
|
|
1471
|
+
const { join: join7, dirname: dirname3 } = await import("path");
|
|
1472
|
+
const fullPath = join7(projectDir, filePath);
|
|
1473
|
+
mkdirSync(dirname3(fullPath), { recursive: true });
|
|
1474
|
+
writeFileSync(fullPath, sql, "utf-8");
|
|
1475
|
+
return { saved: true, path: filePath };
|
|
1476
|
+
});
|
|
1477
|
+
fastify.post("/api/semantic/rebuild", async () => {
|
|
1478
|
+
semanticService.buildFromDisk();
|
|
1479
|
+
const model = semanticService.getModel();
|
|
1480
|
+
return {
|
|
1481
|
+
rebuilt: true,
|
|
1482
|
+
entity_count: model?.entities.length ?? 0,
|
|
1483
|
+
generated_at: model?.generated_at
|
|
1484
|
+
};
|
|
1485
|
+
});
|
|
1486
|
+
}
|
|
1487
|
+
|
|
1488
|
+
// ../server/dist/services/semantic-service.js
|
|
1489
|
+
import { readFileSync, existsSync } from "fs";
|
|
1490
|
+
import { join as join5 } from "path";
|
|
1491
|
+
var SemanticService = class {
|
|
1492
|
+
model = null;
|
|
1493
|
+
builder;
|
|
1494
|
+
compiler = new SemanticQueryCompiler();
|
|
1495
|
+
projectDir;
|
|
1496
|
+
constructor(options) {
|
|
1497
|
+
this.projectDir = options.projectDir;
|
|
1498
|
+
this.builder = new SemanticModelBuilder(options.config);
|
|
1499
|
+
}
|
|
1500
|
+
getModel() {
|
|
1501
|
+
return this.model;
|
|
1502
|
+
}
|
|
1503
|
+
build(input) {
|
|
1504
|
+
this.model = this.builder.build(input);
|
|
1505
|
+
}
|
|
1506
|
+
buildFromDisk(models, chartHints) {
|
|
1507
|
+
const catalogPath = join5(this.projectDir, ".yamchart", "catalog.json");
|
|
1508
|
+
let catalog;
|
|
1509
|
+
if (existsSync(catalogPath)) {
|
|
1510
|
+
const raw = readFileSync(catalogPath, "utf-8");
|
|
1511
|
+
catalog = JSON.parse(raw);
|
|
1512
|
+
}
|
|
1513
|
+
this.build({ catalog, models, chartHints });
|
|
1514
|
+
}
|
|
1515
|
+
compileQuery(query) {
|
|
1516
|
+
if (!this.model) {
|
|
1517
|
+
throw new Error("Semantic model not built. Run build() first.");
|
|
1518
|
+
}
|
|
1519
|
+
validateSemanticQuery(this.model, query);
|
|
1520
|
+
const sql = this.compiler.compile(this.model, query);
|
|
1521
|
+
return {
|
|
1522
|
+
sql,
|
|
1523
|
+
tier: "direct",
|
|
1524
|
+
entity: query.model
|
|
1525
|
+
};
|
|
1526
|
+
}
|
|
1527
|
+
};
|
|
1528
|
+
|
|
1135
1529
|
// ../server/dist/services/slack-notifier.js
|
|
1136
1530
|
var MAX_TABLE_ROWS = 10;
|
|
1137
1531
|
var OPERATOR_SYMBOLS = {
|
|
@@ -4122,8 +4516,8 @@ var RealtimeChannel = class _RealtimeChannel {
|
|
|
4122
4516
|
_trigger(type, payload, ref) {
|
|
4123
4517
|
var _a, _b;
|
|
4124
4518
|
const typeLower = type.toLocaleLowerCase();
|
|
4125
|
-
const { close, error: error2, leave, join:
|
|
4126
|
-
const events = [close, error2, leave,
|
|
4519
|
+
const { close, error: error2, leave, join: join7 } = CHANNEL_EVENTS;
|
|
4520
|
+
const events = [close, error2, leave, join7];
|
|
4127
4521
|
if (ref && events.indexOf(typeLower) >= 0 && ref !== this._joinRef()) {
|
|
4128
4522
|
return;
|
|
4129
4523
|
}
|
|
@@ -13352,6 +13746,10 @@ async function publicRoutes(fastify, options) {
|
|
|
13352
13746
|
return;
|
|
13353
13747
|
const chart = configLoader.getChartByName(name);
|
|
13354
13748
|
if (!chart) {
|
|
13749
|
+
const validationError = configLoader.getValidationError(name);
|
|
13750
|
+
if (validationError) {
|
|
13751
|
+
return reply.status(422).send({ error: `Invalid chart config "${name}" (${validationError.file}): ${validationError.message}` });
|
|
13752
|
+
}
|
|
13355
13753
|
return reply.status(404).send({ error: `Chart not found: ${name}` });
|
|
13356
13754
|
}
|
|
13357
13755
|
try {
|
|
@@ -13390,6 +13788,10 @@ async function publicRoutes(fastify, options) {
|
|
|
13390
13788
|
return;
|
|
13391
13789
|
const chart = configLoader.getChartByName(name);
|
|
13392
13790
|
if (!chart) {
|
|
13791
|
+
const validationError = configLoader.getValidationError(name);
|
|
13792
|
+
if (validationError) {
|
|
13793
|
+
return reply.status(422).send({ error: `Invalid chart config "${name}" (${validationError.file}): ${validationError.message}` });
|
|
13794
|
+
}
|
|
13393
13795
|
return reply.status(404).send({ error: `Chart not found: ${name}` });
|
|
13394
13796
|
}
|
|
13395
13797
|
return {
|
|
@@ -13407,6 +13809,10 @@ async function publicRoutes(fastify, options) {
|
|
|
13407
13809
|
return;
|
|
13408
13810
|
const dashboard = configLoader.getDashboardByName(id);
|
|
13409
13811
|
if (!dashboard) {
|
|
13812
|
+
const validationError = configLoader.getValidationError(id);
|
|
13813
|
+
if (validationError) {
|
|
13814
|
+
return reply.status(422).send({ error: `Invalid dashboard config "${id}" (${validationError.file}): ${validationError.message}` });
|
|
13815
|
+
}
|
|
13410
13816
|
return reply.status(404).send({ error: `Dashboard not found: ${id}` });
|
|
13411
13817
|
}
|
|
13412
13818
|
return {
|
|
@@ -13457,8 +13863,13 @@ async function publicRoutes(fastify, options) {
|
|
|
13457
13863
|
return { name, error: "Access denied" };
|
|
13458
13864
|
}
|
|
13459
13865
|
const chart = configLoader.getChartByName(name);
|
|
13460
|
-
if (!chart)
|
|
13866
|
+
if (!chart) {
|
|
13867
|
+
const validationError = configLoader.getValidationError(name);
|
|
13868
|
+
if (validationError) {
|
|
13869
|
+
return { name, error: `Invalid chart config "${name}" (${validationError.file}): ${validationError.message}` };
|
|
13870
|
+
}
|
|
13461
13871
|
return { name, error: `Chart not found: ${name}` };
|
|
13872
|
+
}
|
|
13462
13873
|
try {
|
|
13463
13874
|
const result = await queryService.executeChart(chart, params);
|
|
13464
13875
|
const response = {
|
|
@@ -13608,7 +14019,7 @@ async function chatRoutes(fastify, options) {
|
|
|
13608
14019
|
systemParts.push("Output ONLY the new text to insert. Do NOT repeat the existing content.");
|
|
13609
14020
|
}
|
|
13610
14021
|
const systemPrompt = systemParts.join("\n");
|
|
13611
|
-
const { AnthropicProvider } = await import("./dist-
|
|
14022
|
+
const { AnthropicProvider } = await import("./dist-WDTDQDTX.js");
|
|
13612
14023
|
const provider = new AnthropicProvider(apiKey);
|
|
13613
14024
|
const response = await provider.chat({
|
|
13614
14025
|
system: systemPrompt,
|
|
@@ -13647,6 +14058,12 @@ async function eventsRoute(fastify) {
|
|
|
13647
14058
|
});
|
|
13648
14059
|
});
|
|
13649
14060
|
}
|
|
14061
|
+
function closeAllClients() {
|
|
14062
|
+
for (const client of clients) {
|
|
14063
|
+
client.end();
|
|
14064
|
+
}
|
|
14065
|
+
clients.clear();
|
|
14066
|
+
}
|
|
13650
14067
|
function broadcastConfigChange() {
|
|
13651
14068
|
const data = JSON.stringify({ type: "config-change", timestamp: Date.now() });
|
|
13652
14069
|
const message = `event: config-change
|
|
@@ -13659,17 +14076,18 @@ data: ${data}
|
|
|
13659
14076
|
}
|
|
13660
14077
|
|
|
13661
14078
|
// ../server/dist/server.js
|
|
13662
|
-
import { join as
|
|
14079
|
+
import { join as join6, dirname as dirname2 } from "path";
|
|
13663
14080
|
import { fileURLToPath } from "url";
|
|
13664
|
-
import { access as access2, readFile as
|
|
14081
|
+
import { access as access2, readFile as readFile3 } from "fs/promises";
|
|
14082
|
+
import { watch as fsWatch } from "fs";
|
|
13665
14083
|
var __dirname = dirname2(fileURLToPath(import.meta.url));
|
|
13666
|
-
var packageJsonPath =
|
|
13667
|
-
var packageJson = JSON.parse(await
|
|
14084
|
+
var packageJsonPath = join6(__dirname, "..", "package.json");
|
|
14085
|
+
var packageJson = JSON.parse(await readFile3(packageJsonPath, "utf-8"));
|
|
13668
14086
|
var VERSION = packageJson.version;
|
|
13669
14087
|
async function enrichRefsFromCatalog(projectDir, refs) {
|
|
13670
14088
|
try {
|
|
13671
|
-
const catalogPath =
|
|
13672
|
-
const catalogData = JSON.parse(await
|
|
14089
|
+
const catalogPath = join6(projectDir, ".yamchart", "catalog.json");
|
|
14090
|
+
const catalogData = JSON.parse(await readFile3(catalogPath, "utf-8"));
|
|
13673
14091
|
for (const model of catalogData.models ?? []) {
|
|
13674
14092
|
if (model.name && model.table) {
|
|
13675
14093
|
refs[model.name] = model.table;
|
|
@@ -13679,7 +14097,7 @@ async function enrichRefsFromCatalog(projectDir, refs) {
|
|
|
13679
14097
|
}
|
|
13680
14098
|
}
|
|
13681
14099
|
async function createServer(options) {
|
|
13682
|
-
const { projectDir, port = 3001, host = "0.0.0.0", watch: watch2 = false, serveStatic = process.env.NODE_ENV === "production", staticDir =
|
|
14100
|
+
const { projectDir, port = 3001, host = "0.0.0.0", watch: watch2 = false, serveStatic = process.env.NODE_ENV === "production", staticDir = join6(__dirname, "public"), auth } = options;
|
|
13683
14101
|
if (auth) {
|
|
13684
14102
|
initAuthServer(auth.supabaseUrl, auth.supabaseServiceKey);
|
|
13685
14103
|
}
|
|
@@ -13734,6 +14152,12 @@ async function createServer(options) {
|
|
|
13734
14152
|
models,
|
|
13735
14153
|
refs
|
|
13736
14154
|
});
|
|
14155
|
+
const semanticService = new SemanticService({
|
|
14156
|
+
projectDir,
|
|
14157
|
+
config: project.semantic
|
|
14158
|
+
});
|
|
14159
|
+
semanticService.buildFromDisk();
|
|
14160
|
+
fastify.log.info(`Semantic model built: ${semanticService.getModel()?.entities.length ?? 0} entities`);
|
|
13737
14161
|
const notifier = new SlackNotifier();
|
|
13738
14162
|
const baseUrl = project.defaults?.base_url ?? process.env.BASE_URL ?? void 0;
|
|
13739
14163
|
const schedulerService = new SchedulerService(queryService, configLoader, notifier, baseUrl);
|
|
@@ -13767,6 +14191,7 @@ async function createServer(options) {
|
|
|
13767
14191
|
});
|
|
13768
14192
|
}
|
|
13769
14193
|
});
|
|
14194
|
+
await fastify.register(semanticRoutes, { semanticService, queryService, projectDir });
|
|
13770
14195
|
if (authDb) {
|
|
13771
14196
|
const secureCookies = process.env.NODE_ENV === "production";
|
|
13772
14197
|
const sessionTtlMs = options.localAuth.sessionTtlMs;
|
|
@@ -13802,6 +14227,7 @@ async function createServer(options) {
|
|
|
13802
14227
|
protectedRoutes.addHook("preHandler", orgMiddleware);
|
|
13803
14228
|
await protectedRoutes.register(configRoutes, { configLoader, projectDir, queryService });
|
|
13804
14229
|
await protectedRoutes.register(chartRoutes, { configLoader, queryService });
|
|
14230
|
+
await protectedRoutes.register(chartEventsRoutes, { configLoader, projectDir });
|
|
13805
14231
|
await protectedRoutes.register(dashboardRoutes, { configLoader, gitService, projectDir, queryService });
|
|
13806
14232
|
await protectedRoutes.register(chatRoutes, { configLoader, queryService, projectDir });
|
|
13807
14233
|
});
|
|
@@ -13811,6 +14237,7 @@ async function createServer(options) {
|
|
|
13811
14237
|
protectedRoutes.addHook("preHandler", localMiddleware);
|
|
13812
14238
|
await protectedRoutes.register(configRoutes, { configLoader, projectDir, queryService });
|
|
13813
14239
|
await protectedRoutes.register(chartRoutes, { configLoader, queryService });
|
|
14240
|
+
await protectedRoutes.register(chartEventsRoutes, { configLoader, projectDir });
|
|
13814
14241
|
await protectedRoutes.register(dashboardRoutes, { configLoader, gitService, projectDir, queryService });
|
|
13815
14242
|
await protectedRoutes.register(chatRoutes, { configLoader, queryService, projectDir });
|
|
13816
14243
|
await protectedRoutes.register(sharesRoutes, { authDb });
|
|
@@ -13818,10 +14245,11 @@ async function createServer(options) {
|
|
|
13818
14245
|
} else {
|
|
13819
14246
|
await fastify.register(configRoutes, { configLoader, projectDir, queryService });
|
|
13820
14247
|
await fastify.register(chartRoutes, { configLoader, queryService });
|
|
14248
|
+
await fastify.register(chartEventsRoutes, { configLoader, projectDir });
|
|
13821
14249
|
await fastify.register(dashboardRoutes, { configLoader, gitService, projectDir, queryService });
|
|
13822
14250
|
await fastify.register(chatRoutes, { configLoader, queryService, projectDir });
|
|
13823
14251
|
}
|
|
13824
|
-
const assetsDir =
|
|
14252
|
+
const assetsDir = join6(projectDir, "assets");
|
|
13825
14253
|
try {
|
|
13826
14254
|
await access2(assetsDir);
|
|
13827
14255
|
await fastify.register(fastifyStatic, {
|
|
@@ -13832,10 +14260,10 @@ async function createServer(options) {
|
|
|
13832
14260
|
} catch {
|
|
13833
14261
|
}
|
|
13834
14262
|
if (project.theme?.customCss) {
|
|
13835
|
-
const cssPath =
|
|
14263
|
+
const cssPath = join6(projectDir, project.theme.customCss);
|
|
13836
14264
|
fastify.get("/api/theme/custom.css", async (_request2, reply) => {
|
|
13837
14265
|
try {
|
|
13838
|
-
const content = await
|
|
14266
|
+
const content = await readFile3(cssPath, "utf-8");
|
|
13839
14267
|
return reply.type("text/css").send(content);
|
|
13840
14268
|
} catch {
|
|
13841
14269
|
return reply.status(404).send("");
|
|
@@ -13873,6 +14301,7 @@ async function createServer(options) {
|
|
|
13873
14301
|
await enrichRefsFromCatalog(projectDir, refs2);
|
|
13874
14302
|
return { models: models2, refs: refs2 };
|
|
13875
14303
|
}
|
|
14304
|
+
let catalogWatcher = null;
|
|
13876
14305
|
if (watch2) {
|
|
13877
14306
|
await fastify.register(eventsRoute);
|
|
13878
14307
|
configLoader.startWatching();
|
|
@@ -13886,8 +14315,18 @@ async function createServer(options) {
|
|
|
13886
14315
|
if (newSchedules.length > 0) {
|
|
13887
14316
|
schedulerService.start(newSchedules);
|
|
13888
14317
|
}
|
|
14318
|
+
semanticService.buildFromDisk();
|
|
14319
|
+
fastify.log.info(`Semantic model rebuilt: ${semanticService.getModel()?.entities.length ?? 0} entities`);
|
|
13889
14320
|
broadcastConfigChange();
|
|
13890
14321
|
});
|
|
14322
|
+
const catalogPath = join6(projectDir, ".yamchart", "catalog.json");
|
|
14323
|
+
try {
|
|
14324
|
+
catalogWatcher = fsWatch(catalogPath, () => {
|
|
14325
|
+
semanticService.buildFromDisk();
|
|
14326
|
+
fastify.log.info(`Semantic model rebuilt (catalog change): ${semanticService.getModel()?.entities.length ?? 0} entities`);
|
|
14327
|
+
});
|
|
14328
|
+
} catch {
|
|
14329
|
+
}
|
|
13891
14330
|
}
|
|
13892
14331
|
return {
|
|
13893
14332
|
fastify,
|
|
@@ -13915,6 +14354,8 @@ async function createServer(options) {
|
|
|
13915
14354
|
await configLoader.stop();
|
|
13916
14355
|
await connector.disconnect();
|
|
13917
14356
|
authDb?.close();
|
|
14357
|
+
catalogWatcher?.close();
|
|
14358
|
+
closeAllClients();
|
|
13918
14359
|
await fastify.close();
|
|
13919
14360
|
}
|
|
13920
14361
|
};
|
|
@@ -13942,9 +14383,9 @@ async function runDevServer(projectDir, options) {
|
|
|
13942
14383
|
let usesBrowserAuth = false;
|
|
13943
14384
|
let localAuth;
|
|
13944
14385
|
try {
|
|
13945
|
-
const { readFileSync } = await import("fs");
|
|
14386
|
+
const { readFileSync: readFileSync2 } = await import("fs");
|
|
13946
14387
|
const { parse } = await import("yaml");
|
|
13947
|
-
const raw =
|
|
14388
|
+
const raw = readFileSync2(resolve(projectDir, "yamchart.yaml"), "utf-8");
|
|
13948
14389
|
const projectConfig = parse(raw);
|
|
13949
14390
|
if (projectConfig?.auth?.enabled) {
|
|
13950
14391
|
localAuth = {
|
|
@@ -13957,7 +14398,7 @@ async function runDevServer(projectDir, options) {
|
|
|
13957
14398
|
const targetConn = process.env.YAMCHART_CONNECTION || projectConfig?.defaults?.connection;
|
|
13958
14399
|
if (targetConn) {
|
|
13959
14400
|
try {
|
|
13960
|
-
const connRaw =
|
|
14401
|
+
const connRaw = readFileSync2(resolve(connectionDir, `${targetConn}.yaml`), "utf-8");
|
|
13961
14402
|
const connConfig = parse(connRaw);
|
|
13962
14403
|
if (connConfig?.auth?.type === "externalbrowser") {
|
|
13963
14404
|
usesBrowserAuth = true;
|
|
@@ -14023,4 +14464,4 @@ async function runDevServer(projectDir, options) {
|
|
|
14023
14464
|
export {
|
|
14024
14465
|
runDevServer
|
|
14025
14466
|
};
|
|
14026
|
-
//# sourceMappingURL=dev-
|
|
14467
|
+
//# sourceMappingURL=dev-MRZ76V74.js.map
|