yamchart 0.8.7 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{advisor-54JBE2EV.js → advisor-Z7TKPPBR.js} +10 -10
- package/dist/agent-KWKPAYT2.js +8 -0
- package/dist/{chunk-NCPWAWIM.js → chunk-AMHCOB4D.js} +4 -4
- package/dist/{chunk-5N3FYFBV.js → chunk-CWAWATL4.js} +34 -7
- package/dist/chunk-CWAWATL4.js.map +1 -0
- package/dist/{chunk-C7A7TKSY.js → chunk-E2QN2M7S.js} +77 -7
- package/dist/chunk-E2QN2M7S.js.map +1 -0
- package/dist/{chunk-YMQ4PWVJ.js → chunk-FZFBBB7K.js} +2 -2
- package/dist/{chunk-GLCEDWGH.js → chunk-G57J2WQM.js} +4 -4
- package/dist/chunk-ZA6AOQVZ.js +677 -0
- package/dist/chunk-ZA6AOQVZ.js.map +1 -0
- package/dist/{connection-utils-MXEF6X7K.js → connection-utils-CTPN7PV3.js} +4 -4
- package/dist/{describe-XKLBZEWG.js → describe-4NME6RCB.js} +5 -5
- package/dist/{dev-Z2R2DBWO.js → dev-6QGAB4ZH.js} +845 -70
- package/dist/dev-6QGAB4ZH.js.map +1 -0
- package/dist/{dist-LJR7TAW4.js → dist-4GUE24QV.js} +4 -2
- package/dist/{dist-GVNWQXFR.js → dist-7CRX2GIR.js} +2 -2
- package/dist/{dist-E2PVGIPT.js → dist-VNX77VV5.js} +4 -2
- package/dist/index.js +21 -21
- package/dist/public/assets/{EventManagement-DtPDwZ-w.js → EventManagement-MMsAkJKj.js} +2 -2
- package/dist/public/assets/ExplorePage-BSkSNgLT.js +1 -0
- package/dist/public/assets/{LoginPage-DBq1qDOK.js → LoginPage-vaI1dnyL.js} +1 -1
- package/dist/public/assets/PublicViewer-B-OKj2cg.js +1 -0
- package/dist/public/assets/{SetupWizard-hgd12cdr.js → SetupWizard-DvlVX2O6.js} +1 -1
- package/dist/public/assets/{ShareManagement-D82oEJJg.js → ShareManagement-ulvPrOAQ.js} +1 -1
- package/dist/public/assets/{UserManagement-CZsxY9aP.js → UserManagement-CvmpNy3o.js} +1 -1
- package/dist/public/assets/{index-B_fusLA_.css → index-CfyF2Wf-.css} +1 -1
- package/dist/public/assets/index-DD59fsOk.js +195 -0
- package/dist/public/assets/{index.es-BmKO-vE1.js → index.es-BeTaRWIv.js} +1 -1
- package/dist/public/assets/{jspdf.es.min-DMVrmE3G.js → jspdf.es.min-9haD1GSE.js} +3 -3
- package/dist/public/index.html +2 -2
- package/dist/{query-MXMFI5TB.js → query-Z75RKTHV.js} +4 -4
- package/dist/{sample-HDPYNAKS.js → sample-OIJNXQNC.js} +4 -4
- package/dist/{search-6CPEPJTI.js → search-YDCPIDZX.js} +5 -5
- package/dist/{source-resolver-PCASPRSD.js → source-resolver-4SUWXUGW.js} +5 -5
- package/dist/source-resolver-4SUWXUGW.js.map +1 -0
- package/dist/{sync-warehouse-XC7YYZKC.js → sync-warehouse-NZFDS6WK.js} +4 -4
- package/dist/{tables-26PNVZIC.js → tables-WJS2VI4L.js} +5 -5
- package/dist/templates/default/CLAUDE.md +1 -1
- package/dist/templates/default/docs/yamchart-reference.md +164 -2
- package/dist/templates/default/yamchart.yaml +3 -0
- package/dist/{test-APA44AIF.js → test-I4XOF7TZ.js} +5 -17
- package/dist/test-I4XOF7TZ.js.map +1 -0
- package/package.json +3 -2
- package/dist/chunk-5N3FYFBV.js.map +0 -1
- package/dist/chunk-C7A7TKSY.js.map +0 -1
- package/dist/dev-Z2R2DBWO.js.map +0 -1
- package/dist/public/assets/PublicViewer-Da8Cu1C1.js +0 -1
- package/dist/public/assets/index-DVSm0iiw.js +0 -187
- package/dist/test-APA44AIF.js.map +0 -1
- /package/dist/{advisor-54JBE2EV.js.map → advisor-Z7TKPPBR.js.map} +0 -0
- /package/dist/{connection-utils-MXEF6X7K.js.map → agent-KWKPAYT2.js.map} +0 -0
- /package/dist/{chunk-NCPWAWIM.js.map → chunk-AMHCOB4D.js.map} +0 -0
- /package/dist/{chunk-YMQ4PWVJ.js.map → chunk-FZFBBB7K.js.map} +0 -0
- /package/dist/{chunk-GLCEDWGH.js.map → chunk-G57J2WQM.js.map} +0 -0
- /package/dist/{dist-E2PVGIPT.js.map → connection-utils-CTPN7PV3.js.map} +0 -0
- /package/dist/{describe-XKLBZEWG.js.map → describe-4NME6RCB.js.map} +0 -0
- /package/dist/{dist-LJR7TAW4.js.map → dist-4GUE24QV.js.map} +0 -0
- /package/dist/{dist-GVNWQXFR.js.map → dist-7CRX2GIR.js.map} +0 -0
- /package/dist/{source-resolver-PCASPRSD.js.map → dist-VNX77VV5.js.map} +0 -0
- /package/dist/{query-MXMFI5TB.js.map → query-Z75RKTHV.js.map} +0 -0
- /package/dist/{sample-HDPYNAKS.js.map → sample-OIJNXQNC.js.map} +0 -0
- /package/dist/{search-6CPEPJTI.js.map → search-YDCPIDZX.js.map} +0 -0
- /package/dist/{sync-warehouse-XC7YYZKC.js.map → sync-warehouse-NZFDS6WK.js.map} +0 -0
- /package/dist/{tables-26PNVZIC.js.map → tables-WJS2VI4L.js.map} +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
loadEnvFile,
|
|
3
3
|
validateProject
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-G57J2WQM.js";
|
|
5
5
|
import {
|
|
6
6
|
box,
|
|
7
7
|
detail,
|
|
@@ -23,7 +23,11 @@ import {
|
|
|
23
23
|
SemanticQuerySchema,
|
|
24
24
|
deepMerge,
|
|
25
25
|
resolveProjectConfig
|
|
26
|
-
} from "./chunk-
|
|
26
|
+
} from "./chunk-E2QN2M7S.js";
|
|
27
|
+
import {
|
|
28
|
+
StreamingChatAgent,
|
|
29
|
+
filterCatalogEntries
|
|
30
|
+
} from "./chunk-ZA6AOQVZ.js";
|
|
27
31
|
import {
|
|
28
32
|
AuthDatabase,
|
|
29
33
|
generateSessionToken,
|
|
@@ -53,7 +57,7 @@ import {
|
|
|
53
57
|
resolveMySQLAuth,
|
|
54
58
|
resolvePostgresAuth,
|
|
55
59
|
resolveSnowflakeAuth
|
|
56
|
-
} from "./chunk-
|
|
60
|
+
} from "./chunk-CWAWATL4.js";
|
|
57
61
|
import {
|
|
58
62
|
SemanticModelBuilder,
|
|
59
63
|
SemanticQueryCompiler,
|
|
@@ -63,7 +67,7 @@ import {
|
|
|
63
67
|
import "./chunk-DGUM43GV.js";
|
|
64
68
|
|
|
65
69
|
// src/commands/dev.ts
|
|
66
|
-
import { resolve } from "path";
|
|
70
|
+
import { resolve as resolve2 } from "path";
|
|
67
71
|
import { homedir } from "os";
|
|
68
72
|
|
|
69
73
|
// ../server/dist/server.js
|
|
@@ -76,6 +80,257 @@ import { readFile, readdir, access } from "fs/promises";
|
|
|
76
80
|
import { join, extname, relative, dirname } from "path";
|
|
77
81
|
import { parse as parseYaml } from "yaml";
|
|
78
82
|
import { watch } from "chokidar";
|
|
83
|
+
|
|
84
|
+
// ../../packages/chat/dist/registry.js
|
|
85
|
+
import { parse } from "yaml";
|
|
86
|
+
|
|
87
|
+
// ../../packages/chat/dist/agents/dashboard-analyst.js
|
|
88
|
+
var DASHBOARD_ANALYST = {
|
|
89
|
+
name: "dashboard-analyst",
|
|
90
|
+
title: "Dashboard Analyst",
|
|
91
|
+
description: "Summarizes dashboards, explains trends, answers data questions, and can apply filters to the dashboard.",
|
|
92
|
+
systemPrompt: `You are an AI dashboard analyst for a business intelligence platform.
|
|
93
|
+
|
|
94
|
+
Your job is to help users understand their data by:
|
|
95
|
+
- Summarizing what a dashboard or chart shows
|
|
96
|
+
- Explaining trends, outliers, and patterns in the data
|
|
97
|
+
- Answering questions about metrics and KPIs
|
|
98
|
+
- Suggesting and applying filters to explore data
|
|
99
|
+
- Running ad-hoc SQL to explore data beyond existing charts
|
|
100
|
+
|
|
101
|
+
## Rules
|
|
102
|
+
- Be concise. Dashboard users want quick answers, not essays.
|
|
103
|
+
- Use numbers and percentages when discussing data.
|
|
104
|
+
- When you query data, summarize the key findings \u2014 don't dump raw rows.
|
|
105
|
+
- If asked about a trend, explain the direction, magnitude, and possible causes.
|
|
106
|
+
- When suggesting filters, explain what you expect to see.
|
|
107
|
+
- Use the apply_filter tool when the user asks to filter or drill down.
|
|
108
|
+
- If the existing charts don't have the data needed, use run_sql to query the database directly.
|
|
109
|
+
- Use call_agent to delegate to specialized agents when you need expertise you don't have.
|
|
110
|
+
- Use suggest_actions to offer follow-up exploration or save options after answering.
|
|
111
|
+
- If you don't have enough data to answer, say so and suggest what to query.
|
|
112
|
+
|
|
113
|
+
## Data Visualization in Responses
|
|
114
|
+
When you have tabular data to share, use these markdown code fences:
|
|
115
|
+
|
|
116
|
+
For charts (line, bar, pie, scatter):
|
|
117
|
+
\`\`\`chart
|
|
118
|
+
{"type":"line","data":[{"date":"2025-01-01","value":100},...],"x":"date","y":"value","title":"Optional Title"}
|
|
119
|
+
\`\`\`
|
|
120
|
+
|
|
121
|
+
For data tables:
|
|
122
|
+
\`\`\`data
|
|
123
|
+
{"columns":[{"name":"date","type":"date"},{"name":"value","type":"number"}],"rows":[{"date":"2025-01-01","value":100},...]}
|
|
124
|
+
\`\`\`
|
|
125
|
+
|
|
126
|
+
Choose charts for trends and comparisons, tables for detailed breakdowns. Keep data concise \u2014 summarize large result sets.`,
|
|
127
|
+
tools: ["query_model", "get_chart_config", "get_dashboard_summary", "apply_filter", "compare_periods", "run_sql", "call_agent", "get_catalog_tables", "suggest_actions", "save_model", "save_chart", "add_to_dashboard"],
|
|
128
|
+
builtIn: true
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
// ../../packages/chat/dist/agents/sql-explorer.js
|
|
132
|
+
var SQL_EXPLORER = {
|
|
133
|
+
name: "sql-explorer",
|
|
134
|
+
title: "SQL Explorer",
|
|
135
|
+
description: "Runs ad-hoc SQL queries against the connected database for deep data exploration and period comparisons.",
|
|
136
|
+
systemPrompt: `You are a SQL expert helping users explore their data warehouse.
|
|
137
|
+
|
|
138
|
+
Your job is to:
|
|
139
|
+
- Write and execute SQL queries to answer user questions
|
|
140
|
+
- Compare data across time periods
|
|
141
|
+
- Find specific records or aggregations
|
|
142
|
+
- Explain query results clearly
|
|
143
|
+
|
|
144
|
+
## Rules
|
|
145
|
+
- Always LIMIT your queries (default: 50 rows) to avoid large payloads.
|
|
146
|
+
- Use clear column aliases so results are readable.
|
|
147
|
+
- When comparing periods, use the compare_periods tool for models, or write explicit SQL for ad-hoc comparisons.
|
|
148
|
+
- Explain what your query does before showing results.
|
|
149
|
+
- If a query fails, explain the error and try a corrected version.
|
|
150
|
+
- Never run destructive queries (INSERT, UPDATE, DELETE, DROP, etc.).
|
|
151
|
+
|
|
152
|
+
## Data Visualization in Responses
|
|
153
|
+
When you have tabular data to share, use these markdown code fences:
|
|
154
|
+
|
|
155
|
+
For charts (line, bar, pie, scatter):
|
|
156
|
+
\`\`\`chart
|
|
157
|
+
{"type":"line","data":[{"date":"2025-01-01","value":100},...],"x":"date","y":"value","title":"Optional Title"}
|
|
158
|
+
\`\`\`
|
|
159
|
+
|
|
160
|
+
For data tables:
|
|
161
|
+
\`\`\`data
|
|
162
|
+
{"columns":[{"name":"date","type":"date"},{"name":"value","type":"number"}],"rows":[{"date":"2025-01-01","value":100},...]}
|
|
163
|
+
\`\`\`
|
|
164
|
+
|
|
165
|
+
Choose charts for trends and comparisons, tables for detailed breakdowns. Keep data concise \u2014 summarize large result sets.`,
|
|
166
|
+
tools: ["query_model", "run_sql", "compare_periods", "get_chart_config", "call_agent", "get_catalog_tables", "suggest_actions", "save_model", "save_chart"],
|
|
167
|
+
builtIn: true
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
// ../../packages/chat/dist/agents/data-explorer.js
|
|
171
|
+
var DATA_EXPLORER = {
|
|
172
|
+
name: "data-explorer",
|
|
173
|
+
title: "Data Explorer",
|
|
174
|
+
description: "Open-ended data exploration, visualization, and dashboard building.",
|
|
175
|
+
systemPrompt: `You are an AI data explorer for a business intelligence platform.
|
|
176
|
+
|
|
177
|
+
Your job is to help users explore their data warehouse, build visualizations, and create dashboards \u2014 starting from curiosity and ending with saved, reusable charts.
|
|
178
|
+
|
|
179
|
+
## Discovery First
|
|
180
|
+
- Always start with get_catalog_tables to understand what data is available before writing SQL.
|
|
181
|
+
- Prefer querying named models (query_model) over raw SQL when a model already exists.
|
|
182
|
+
- When no model exists, use run_sql to explore \u2014 then offer to save the query as a model.
|
|
183
|
+
|
|
184
|
+
## Rules
|
|
185
|
+
- All SQL must be read-only (SELECT only). Never run INSERT, UPDATE, DELETE, DROP, ALTER, TRUNCATE, or CREATE.
|
|
186
|
+
- Always LIMIT queries to 50 rows by default. Use LIMIT explicitly.
|
|
187
|
+
- Use clear column aliases so results are readable.
|
|
188
|
+
- When a query fails, explain the error and try a corrected version.
|
|
189
|
+
- Explain what you're querying and why before showing results.
|
|
190
|
+
|
|
191
|
+
## Proactive Visualization
|
|
192
|
+
After retrieving data, always visualize it \u2014 don't just dump raw rows. Choose the right form:
|
|
193
|
+
- Trends over time \u2192 line chart
|
|
194
|
+
- Comparisons across categories \u2192 bar chart
|
|
195
|
+
- Part-to-whole \u2192 pie chart
|
|
196
|
+
- Single key metric \u2192 show as bold text or KPI
|
|
197
|
+
- Detailed breakdowns \u2192 data table
|
|
198
|
+
|
|
199
|
+
For charts (line, bar, pie, scatter):
|
|
200
|
+
\`\`\`chart
|
|
201
|
+
{"type":"line","data":[{"date":"2025-01-01","value":100},...],"x":"date","y":"value","title":"Optional Title"}
|
|
202
|
+
\`\`\`
|
|
203
|
+
|
|
204
|
+
For data tables:
|
|
205
|
+
\`\`\`data
|
|
206
|
+
{"columns":[{"name":"date","type":"date"},{"name":"value","type":"number"}],"rows":[{"date":"2025-01-01","value":100},...]}
|
|
207
|
+
\`\`\`
|
|
208
|
+
|
|
209
|
+
Keep visualizations concise \u2014 summarize large result sets rather than showing every row.
|
|
210
|
+
|
|
211
|
+
## Saving Work
|
|
212
|
+
When a user wants to keep a query or chart:
|
|
213
|
+
1. Use save_model to create a reusable SQL model file.
|
|
214
|
+
2. Use save_chart to create a chart YAML referencing that model.
|
|
215
|
+
3. Use create_dashboard to assemble saved charts into a new dashboard.
|
|
216
|
+
4. After saving, use suggest_actions to offer navigation or further exploration.
|
|
217
|
+
|
|
218
|
+
## Suggesting Next Steps
|
|
219
|
+
Always use suggest_actions after answering a question to offer natural follow-up options:
|
|
220
|
+
- Explore related dimensions or time periods
|
|
221
|
+
- Save the current query as a model
|
|
222
|
+
- Create a chart from the data
|
|
223
|
+
- Add the chart to a dashboard
|
|
224
|
+
|
|
225
|
+
## Collaboration
|
|
226
|
+
Use call_agent to delegate to specialized agents when needed:
|
|
227
|
+
- Dashboard Analyst for questions about existing dashboards
|
|
228
|
+
- SQL Explorer for complex multi-step SQL investigations`,
|
|
229
|
+
tools: ["run_sql", "query_model", "get_catalog_tables", "compare_periods", "save_model", "save_chart", "create_dashboard", "suggest_actions", "call_agent"],
|
|
230
|
+
builtIn: true
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
// ../../packages/chat/dist/registry.js
|
|
234
|
+
var AgentRegistry = class {
|
|
235
|
+
agents = /* @__PURE__ */ new Map();
|
|
236
|
+
constructor() {
|
|
237
|
+
this.agents.set(DASHBOARD_ANALYST.name, DASHBOARD_ANALYST);
|
|
238
|
+
this.agents.set(SQL_EXPLORER.name, SQL_EXPLORER);
|
|
239
|
+
this.agents.set(DATA_EXPLORER.name, DATA_EXPLORER);
|
|
240
|
+
}
|
|
241
|
+
getAgent(name) {
|
|
242
|
+
return this.agents.get(name);
|
|
243
|
+
}
|
|
244
|
+
listAgents() {
|
|
245
|
+
return Array.from(this.agents.values());
|
|
246
|
+
}
|
|
247
|
+
registerAgent(config) {
|
|
248
|
+
this.agents.set(config.name, config);
|
|
249
|
+
}
|
|
250
|
+
loadFromYaml(yamlContent, filePath, onWarning) {
|
|
251
|
+
try {
|
|
252
|
+
const raw = parse(yamlContent);
|
|
253
|
+
if (!raw || typeof raw !== "object") {
|
|
254
|
+
onWarning?.(`Skipping ${filePath}: not a valid YAML object`);
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
if (!raw.name || typeof raw.name !== "string") {
|
|
258
|
+
onWarning?.(`Skipping ${filePath}: missing required "name" field`);
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
if (!raw.description || typeof raw.description !== "string") {
|
|
262
|
+
onWarning?.(`Skipping ${filePath}: missing required "description" field`);
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
if (!raw.system_prompt || typeof raw.system_prompt !== "string") {
|
|
266
|
+
onWarning?.(`Skipping ${filePath}: missing required "system_prompt" field`);
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
const config = {
|
|
270
|
+
name: raw.name,
|
|
271
|
+
title: raw.title ?? raw.name,
|
|
272
|
+
description: raw.description,
|
|
273
|
+
systemPrompt: raw.system_prompt,
|
|
274
|
+
tools: Array.isArray(raw.tools) ? raw.tools.filter((t) => typeof t === "string") : [],
|
|
275
|
+
builtIn: false,
|
|
276
|
+
model: typeof raw.model === "string" ? raw.model : void 0,
|
|
277
|
+
models: Array.isArray(raw.models) ? raw.models.filter((m) => typeof m === "string") : void 0
|
|
278
|
+
};
|
|
279
|
+
this.agents.set(config.name, config);
|
|
280
|
+
} catch (err) {
|
|
281
|
+
onWarning?.(`Skipping ${filePath}: ${err instanceof Error ? err.message : String(err)}`);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
clearProjectAgents() {
|
|
285
|
+
for (const [name, config] of this.agents) {
|
|
286
|
+
if (!config.builtIn)
|
|
287
|
+
this.agents.delete(name);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
};
|
|
291
|
+
|
|
292
|
+
// ../../packages/chat/dist/router.js
|
|
293
|
+
var SQL_PATTERNS = [
|
|
294
|
+
/\bselect\b.*\bfrom\b/i,
|
|
295
|
+
/\brun\s+(a\s+)?query\b/i,
|
|
296
|
+
/\bwrite\s+(a\s+)?query\b/i,
|
|
297
|
+
/\bshow\s+(me\s+)?the\s+sql\b/i,
|
|
298
|
+
/\bexecute\s+sql\b/i,
|
|
299
|
+
/\bjoin\b.*\btable/i
|
|
300
|
+
];
|
|
301
|
+
var DEFAULT_AGENT = "dashboard-analyst";
|
|
302
|
+
var RouterAgent = class {
|
|
303
|
+
registry;
|
|
304
|
+
constructor(registry) {
|
|
305
|
+
this.registry = registry;
|
|
306
|
+
}
|
|
307
|
+
selectAgent(message, context, explicitAgent) {
|
|
308
|
+
if (explicitAgent) {
|
|
309
|
+
const agent = this.registry.getAgent(explicitAgent);
|
|
310
|
+
if (agent)
|
|
311
|
+
return agent;
|
|
312
|
+
}
|
|
313
|
+
if (context?.type === "explore") {
|
|
314
|
+
return this.registry.getAgent("data-explorer") || this.registry.getAgent(DEFAULT_AGENT);
|
|
315
|
+
}
|
|
316
|
+
if (SQL_PATTERNS.some((pattern) => pattern.test(message))) {
|
|
317
|
+
const sqlExplorer = this.registry.getAgent("sql-explorer");
|
|
318
|
+
if (sqlExplorer)
|
|
319
|
+
return sqlExplorer;
|
|
320
|
+
}
|
|
321
|
+
return this.registry.getAgent(DEFAULT_AGENT);
|
|
322
|
+
}
|
|
323
|
+
};
|
|
324
|
+
|
|
325
|
+
// ../../packages/chat/dist/stream.js
|
|
326
|
+
function formatSSEEvent(event) {
|
|
327
|
+
return `event: ${event.type}
|
|
328
|
+
data: ${JSON.stringify(event.data)}
|
|
329
|
+
|
|
330
|
+
`;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// ../server/dist/services/config-loader.js
|
|
79
334
|
var ConfigLoader = class {
|
|
80
335
|
projectDir;
|
|
81
336
|
project = null;
|
|
@@ -92,6 +347,7 @@ var ConfigLoader = class {
|
|
|
92
347
|
validationErrors = /* @__PURE__ */ new Map();
|
|
93
348
|
watcher = null;
|
|
94
349
|
onChangeCallbacks = [];
|
|
350
|
+
agentRegistry = new AgentRegistry();
|
|
95
351
|
env;
|
|
96
352
|
constructor(projectDir, env) {
|
|
97
353
|
this.projectDir = projectDir;
|
|
@@ -116,6 +372,7 @@ var ConfigLoader = class {
|
|
|
116
372
|
await this.loadDashboards();
|
|
117
373
|
await this.loadSchedules();
|
|
118
374
|
await this.loadEvents();
|
|
375
|
+
await this.loadAgents();
|
|
119
376
|
}
|
|
120
377
|
async loadProject() {
|
|
121
378
|
const projectPath = join(this.projectDir, "yamchart.yaml");
|
|
@@ -321,6 +578,20 @@ var ConfigLoader = class {
|
|
|
321
578
|
console.warn(`Invalid events.yaml: ${result.error.message}`);
|
|
322
579
|
}
|
|
323
580
|
}
|
|
581
|
+
async loadAgents() {
|
|
582
|
+
const agentsDir = join(this.projectDir, "agents");
|
|
583
|
+
this.agentRegistry.clearProjectAgents();
|
|
584
|
+
try {
|
|
585
|
+
const files = await readdir(agentsDir);
|
|
586
|
+
for (const file of files.filter((f) => f.endsWith(".yaml") || f.endsWith(".yml"))) {
|
|
587
|
+
const content = await readFile(join(agentsDir, file), "utf-8");
|
|
588
|
+
this.agentRegistry.loadFromYaml(content, `agents/${file}`, (msg) => {
|
|
589
|
+
console.warn(msg);
|
|
590
|
+
});
|
|
591
|
+
}
|
|
592
|
+
} catch {
|
|
593
|
+
}
|
|
594
|
+
}
|
|
324
595
|
startWatching() {
|
|
325
596
|
if (this.watcher)
|
|
326
597
|
return;
|
|
@@ -332,7 +603,8 @@ var ConfigLoader = class {
|
|
|
332
603
|
join(this.projectDir, "models"),
|
|
333
604
|
join(this.projectDir, "dashboards"),
|
|
334
605
|
join(this.projectDir, "schedules"),
|
|
335
|
-
join(this.projectDir, "events.yaml")
|
|
606
|
+
join(this.projectDir, "events.yaml"),
|
|
607
|
+
join(this.projectDir, "agents")
|
|
336
608
|
];
|
|
337
609
|
this.watcher = watch(watchPaths, {
|
|
338
610
|
ignored: /(^|[\/\\])\../,
|
|
@@ -453,6 +725,9 @@ Fix: run "ulimit -n 10240" before starting yamchart dev, or add it to your shell
|
|
|
453
725
|
getValidationErrors() {
|
|
454
726
|
return this.validationErrors;
|
|
455
727
|
}
|
|
728
|
+
getAgentRegistry() {
|
|
729
|
+
return this.agentRegistry;
|
|
730
|
+
}
|
|
456
731
|
};
|
|
457
732
|
|
|
458
733
|
// ../server/dist/services/cache.js
|
|
@@ -529,12 +804,18 @@ function parseTtl2(ttl) {
|
|
|
529
804
|
}
|
|
530
805
|
|
|
531
806
|
// ../server/dist/services/query-service.js
|
|
807
|
+
function quoteIdentifier(name, dialect) {
|
|
808
|
+
if (dialect === "mysql")
|
|
809
|
+
return `\`${name.replace(/`/g, "``")}\``;
|
|
810
|
+
return `"${name.replace(/"/g, '""')}"`;
|
|
811
|
+
}
|
|
532
812
|
var QueryService = class {
|
|
533
813
|
compiler;
|
|
534
814
|
connector;
|
|
535
815
|
cache;
|
|
536
816
|
models;
|
|
537
817
|
refs;
|
|
818
|
+
dialect;
|
|
538
819
|
constructor(config) {
|
|
539
820
|
this.compiler = new QueryCompiler({
|
|
540
821
|
models: config.models,
|
|
@@ -544,6 +825,7 @@ var QueryService = class {
|
|
|
544
825
|
this.cache = config.cache;
|
|
545
826
|
this.models = new Map(Object.entries(config.models));
|
|
546
827
|
this.refs = config.refs;
|
|
828
|
+
this.dialect = config.dialect ?? "duckdb";
|
|
547
829
|
}
|
|
548
830
|
async executeChart(chart, params, userContext) {
|
|
549
831
|
const compiled = this.compiler.compile(chart, params, userContext);
|
|
@@ -575,6 +857,120 @@ var QueryService = class {
|
|
|
575
857
|
compiledSql: compiled.sql
|
|
576
858
|
};
|
|
577
859
|
}
|
|
860
|
+
/**
|
|
861
|
+
* Execute a table chart with server-side pagination.
|
|
862
|
+
* Wraps the compiled SQL with LIMIT/OFFSET and runs a parallel COUNT(*) query.
|
|
863
|
+
* Optionally computes summary aggregations for specified columns.
|
|
864
|
+
*/
|
|
865
|
+
async executeChartPaginated(chart, params, options, userContext) {
|
|
866
|
+
const compiled = this.compiler.compile(chart, params, userContext);
|
|
867
|
+
const baseSql = compiled.sql;
|
|
868
|
+
const q = quoteIdentifier;
|
|
869
|
+
const d = this.dialect;
|
|
870
|
+
let orderBy = "";
|
|
871
|
+
if (options.sortField) {
|
|
872
|
+
const cleanField = options.sortField.replace(/[^\w\s]/g, "");
|
|
873
|
+
if (cleanField.length > 0) {
|
|
874
|
+
orderBy = `ORDER BY ${q(cleanField, d)} ${options.sortDirection === "desc" ? "DESC" : "ASC"}`;
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
const offset = options.page * options.pageSize;
|
|
878
|
+
const dataSql = `SELECT * FROM (${baseSql}) _t ${orderBy} LIMIT ${options.pageSize} OFFSET ${offset}`;
|
|
879
|
+
const countCacheKey = `${compiled.cacheKey}:count`;
|
|
880
|
+
let totalCount;
|
|
881
|
+
const cachedCount = await this.cache.get(countCacheKey);
|
|
882
|
+
if (cachedCount) {
|
|
883
|
+
totalCount = cachedCount.rowCount;
|
|
884
|
+
} else {
|
|
885
|
+
const countSql = `SELECT COUNT(*) AS _count FROM (${baseSql}) _t`;
|
|
886
|
+
const countResult = await this.connector.execute(countSql);
|
|
887
|
+
totalCount = Number(countResult.rows[0]?._count ?? 0);
|
|
888
|
+
await this.cache.set(countCacheKey, {
|
|
889
|
+
columns: [],
|
|
890
|
+
rows: [],
|
|
891
|
+
rowCount: totalCount,
|
|
892
|
+
durationMs: countResult.durationMs,
|
|
893
|
+
cachedAt: Date.now()
|
|
894
|
+
});
|
|
895
|
+
}
|
|
896
|
+
let summaryValues;
|
|
897
|
+
if (options.summaryColumns && options.summaryColumns.length > 0) {
|
|
898
|
+
const summaryCacheKey = `${compiled.cacheKey}:summary`;
|
|
899
|
+
const cachedSummary = await this.cache.get(summaryCacheKey);
|
|
900
|
+
if (cachedSummary && cachedSummary.rows[0]) {
|
|
901
|
+
summaryValues = cachedSummary.rows[0];
|
|
902
|
+
} else {
|
|
903
|
+
const aggExprs = options.summaryColumns.map(({ field, summary }) => {
|
|
904
|
+
const col = q(field, d);
|
|
905
|
+
switch (summary) {
|
|
906
|
+
case "sum":
|
|
907
|
+
return `SUM(${col}) AS ${q(`_sum_${field}`, d)}`;
|
|
908
|
+
case "avg":
|
|
909
|
+
return `AVG(${col}) AS ${q(`_avg_${field}`, d)}`;
|
|
910
|
+
case "min":
|
|
911
|
+
return `MIN(${col}) AS ${q(`_min_${field}`, d)}`;
|
|
912
|
+
case "max":
|
|
913
|
+
return `MAX(${col}) AS ${q(`_max_${field}`, d)}`;
|
|
914
|
+
case "count":
|
|
915
|
+
return `COUNT(${col}) AS ${q(`_count_${field}`, d)}`;
|
|
916
|
+
default:
|
|
917
|
+
return null;
|
|
918
|
+
}
|
|
919
|
+
}).filter(Boolean);
|
|
920
|
+
if (aggExprs.length > 0) {
|
|
921
|
+
const summarySql = `SELECT ${aggExprs.join(", ")} FROM (${baseSql}) _t`;
|
|
922
|
+
const summaryResult = await this.connector.execute(summarySql);
|
|
923
|
+
if (summaryResult.rows[0]) {
|
|
924
|
+
summaryValues = {};
|
|
925
|
+
for (const { field, summary } of options.summaryColumns) {
|
|
926
|
+
const key = `_${summary}_${field}`;
|
|
927
|
+
summaryValues[`${summary}:${field}`] = Number(summaryResult.rows[0][key] ?? 0);
|
|
928
|
+
}
|
|
929
|
+
await this.cache.set(summaryCacheKey, {
|
|
930
|
+
columns: [],
|
|
931
|
+
rows: [summaryValues],
|
|
932
|
+
rowCount: 0,
|
|
933
|
+
durationMs: summaryResult.durationMs,
|
|
934
|
+
cachedAt: Date.now()
|
|
935
|
+
});
|
|
936
|
+
}
|
|
937
|
+
}
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
const result = await this.connector.execute(dataSql);
|
|
941
|
+
return {
|
|
942
|
+
...result,
|
|
943
|
+
cached: false,
|
|
944
|
+
cacheKey: compiled.cacheKey,
|
|
945
|
+
compiledSql: dataSql,
|
|
946
|
+
totalCount,
|
|
947
|
+
page: options.page,
|
|
948
|
+
pageSize: options.pageSize,
|
|
949
|
+
summaryValues
|
|
950
|
+
};
|
|
951
|
+
}
|
|
952
|
+
/**
|
|
953
|
+
* Execute a chart query for CSV export with a bounded row limit.
|
|
954
|
+
* Returns full QueryResult — caller streams to HTTP response.
|
|
955
|
+
*/
|
|
956
|
+
async executeChartForExport(chart, params, maxRows, userContext) {
|
|
957
|
+
const compiled = this.compiler.compile(chart, params, userContext);
|
|
958
|
+
const sql = `SELECT * FROM (${compiled.sql}) _t LIMIT ${maxRows}`;
|
|
959
|
+
const result = await this.connector.execute(sql);
|
|
960
|
+
return {
|
|
961
|
+
...result,
|
|
962
|
+
cached: false,
|
|
963
|
+
cacheKey: compiled.cacheKey,
|
|
964
|
+
compiledSql: sql
|
|
965
|
+
};
|
|
966
|
+
}
|
|
967
|
+
/**
|
|
968
|
+
* Compile a chart query without executing it.
|
|
969
|
+
* Returns the compiled SQL and cache key.
|
|
970
|
+
*/
|
|
971
|
+
compileChart(chart, params, userContext) {
|
|
972
|
+
return this.compiler.compile(chart, params, userContext);
|
|
973
|
+
}
|
|
578
974
|
invalidateChart(chartName) {
|
|
579
975
|
this.cache.invalidatePattern(`${chartName}:*`);
|
|
580
976
|
}
|
|
@@ -857,6 +1253,12 @@ function classifyError(error2) {
|
|
|
857
1253
|
}
|
|
858
1254
|
return "query_error";
|
|
859
1255
|
}
|
|
1256
|
+
function escapeCSVField(value) {
|
|
1257
|
+
if (value.includes(",") || value.includes('"') || value.includes("\n") || value.includes("\r")) {
|
|
1258
|
+
return `"${value.replace(/"/g, '""')}"`;
|
|
1259
|
+
}
|
|
1260
|
+
return value;
|
|
1261
|
+
}
|
|
860
1262
|
function getUserContext(request) {
|
|
861
1263
|
const user = request.user;
|
|
862
1264
|
if (!user)
|
|
@@ -921,15 +1323,78 @@ async function chartRoutes(fastify, options) {
|
|
|
921
1323
|
});
|
|
922
1324
|
fastify.post("/api/charts/:name/query", async (request, reply) => {
|
|
923
1325
|
const { name } = request.params;
|
|
924
|
-
const
|
|
1326
|
+
const rawParams = request.body ?? {};
|
|
925
1327
|
const includeConfig = request.query.includeConfig === "true";
|
|
926
1328
|
const chart = configLoader.getChartByName(name);
|
|
927
1329
|
if (!chart) {
|
|
928
1330
|
const resp = chartNotFoundResponse(configLoader, name);
|
|
929
1331
|
return reply.status(resp.status).send(resp.body);
|
|
930
1332
|
}
|
|
1333
|
+
const { _page, _page_size, _sort_field, _sort_dir, _server_paginated, ...params } = rawParams;
|
|
931
1334
|
try {
|
|
932
|
-
const
|
|
1335
|
+
const isTable = chart.chart?.type === "table";
|
|
1336
|
+
const project = configLoader.getProject();
|
|
1337
|
+
const chartMaxRows = chart.chart?.max_rows;
|
|
1338
|
+
const maxDisplayRows = chartMaxRows ?? project?.defaults?.table?.max_display_rows ?? 1e4;
|
|
1339
|
+
const forceServerSide = chart.chart?.pagination && chart.chart.pagination?.server_side === true;
|
|
1340
|
+
if (isTable && _server_paginated) {
|
|
1341
|
+
const page = Number(_page) || 0;
|
|
1342
|
+
const pageSize = Number(_page_size) || 50;
|
|
1343
|
+
const columns = chart.chart?.columns ?? [];
|
|
1344
|
+
const summaryColumns = columns.filter((c) => c.summary).map((c) => ({ field: c.field, summary: c.summary }));
|
|
1345
|
+
const paginatedResult = await queryService.executeChartPaginated(chart, params, { page, pageSize, sortField: _sort_field, sortDirection: _sort_dir, summaryColumns }, getUserContext(request));
|
|
1346
|
+
reply.header("X-Cache", "MISS");
|
|
1347
|
+
reply.header("X-Query-Duration-Ms", paginatedResult.durationMs.toFixed(0));
|
|
1348
|
+
const response2 = {
|
|
1349
|
+
columns: paginatedResult.columns,
|
|
1350
|
+
rows: paginatedResult.rows,
|
|
1351
|
+
meta: {
|
|
1352
|
+
cached: paginatedResult.cached,
|
|
1353
|
+
durationMs: paginatedResult.durationMs,
|
|
1354
|
+
rowCount: paginatedResult.rowCount,
|
|
1355
|
+
cacheKey: paginatedResult.cacheKey,
|
|
1356
|
+
compiledSql: paginatedResult.compiledSql,
|
|
1357
|
+
serverPaginated: true,
|
|
1358
|
+
totalCount: paginatedResult.totalCount,
|
|
1359
|
+
page,
|
|
1360
|
+
pageSize
|
|
1361
|
+
},
|
|
1362
|
+
...paginatedResult.summaryValues ? { summaryValues: paginatedResult.summaryValues } : {}
|
|
1363
|
+
};
|
|
1364
|
+
if (includeConfig) {
|
|
1365
|
+
response2.config = {
|
|
1366
|
+
name: chart.name,
|
|
1367
|
+
title: chart.title,
|
|
1368
|
+
description: chart.description,
|
|
1369
|
+
parameters: chart.parameters ?? [],
|
|
1370
|
+
chart: chart.chart,
|
|
1371
|
+
drillDown: chart.drillDown
|
|
1372
|
+
};
|
|
1373
|
+
}
|
|
1374
|
+
return response2;
|
|
1375
|
+
}
|
|
1376
|
+
let result;
|
|
1377
|
+
let serverPaginated = false;
|
|
1378
|
+
let totalCount;
|
|
1379
|
+
if (isTable) {
|
|
1380
|
+
const probeResult = await queryService.executeChartForExport(chart, params, maxDisplayRows + 1, getUserContext(request));
|
|
1381
|
+
if (probeResult.rowCount > maxDisplayRows || forceServerSide) {
|
|
1382
|
+
serverPaginated = true;
|
|
1383
|
+
if (probeResult.rowCount > maxDisplayRows) {
|
|
1384
|
+
const compiled = queryService.compileChart(chart, params, getUserContext(request));
|
|
1385
|
+
const countResult = await queryService.executeRawSql(`SELECT COUNT(*) AS _count FROM (${compiled.sql}) _t`);
|
|
1386
|
+
totalCount = Number(countResult.rows[0]?._count ?? 0);
|
|
1387
|
+
result = { ...probeResult, rows: probeResult.rows.slice(0, maxDisplayRows), rowCount: maxDisplayRows };
|
|
1388
|
+
} else {
|
|
1389
|
+
totalCount = probeResult.rowCount;
|
|
1390
|
+
result = probeResult;
|
|
1391
|
+
}
|
|
1392
|
+
} else {
|
|
1393
|
+
result = probeResult;
|
|
1394
|
+
}
|
|
1395
|
+
} else {
|
|
1396
|
+
result = await queryService.executeChart(chart, params, getUserContext(request));
|
|
1397
|
+
}
|
|
933
1398
|
let comparison = void 0;
|
|
934
1399
|
if (chart.chart?.type === "kpi" && chart.chart?.comparison?.period === "previous") {
|
|
935
1400
|
let startDate = params.start_date;
|
|
@@ -972,6 +1437,54 @@ async function chartRoutes(fastify, options) {
|
|
|
972
1437
|
}
|
|
973
1438
|
}
|
|
974
1439
|
}
|
|
1440
|
+
const compareConfig = chart.chart?.compare;
|
|
1441
|
+
let compareData;
|
|
1442
|
+
if (compareConfig && compareConfig.length > 0) {
|
|
1443
|
+
let startDate = params.start_date;
|
|
1444
|
+
let endDate = params.end_date;
|
|
1445
|
+
const dateRange = params.date_range;
|
|
1446
|
+
if (!startDate || !endDate) {
|
|
1447
|
+
if (isRelativeDateRange(params.date_range)) {
|
|
1448
|
+
const resolved = expandRelativeDateRange(params.date_range);
|
|
1449
|
+
startDate = resolved.start_date;
|
|
1450
|
+
endDate = resolved.end_date;
|
|
1451
|
+
} else if (isCustomDateRange(params.date_range)) {
|
|
1452
|
+
const resolved = expandCustomDateRange(params.date_range);
|
|
1453
|
+
startDate = resolved.start_date;
|
|
1454
|
+
endDate = resolved.end_date;
|
|
1455
|
+
} else if (dateRange && isDatePreset(dateRange)) {
|
|
1456
|
+
const resolved = expandDatePreset(dateRange);
|
|
1457
|
+
if (resolved) {
|
|
1458
|
+
startDate = resolved.start_date;
|
|
1459
|
+
endDate = resolved.end_date;
|
|
1460
|
+
}
|
|
1461
|
+
}
|
|
1462
|
+
}
|
|
1463
|
+
if (startDate && endDate) {
|
|
1464
|
+
compareData = [];
|
|
1465
|
+
for (const comp of compareConfig) {
|
|
1466
|
+
try {
|
|
1467
|
+
const periodMap = {
|
|
1468
|
+
previous_week: "last_7_days",
|
|
1469
|
+
previous_month: "last_30_days",
|
|
1470
|
+
previous_quarter: "last_90_days",
|
|
1471
|
+
previous_year: "last_365_days"
|
|
1472
|
+
};
|
|
1473
|
+
const prev = computePreviousPeriod(startDate, endDate, periodMap[comp.period] ?? comp.period);
|
|
1474
|
+
const prevLabel = comp.label ?? comp.period.replace(/_/g, " ");
|
|
1475
|
+
const { date_range: _, ...restParams } = params;
|
|
1476
|
+
const prevParams = { ...restParams, start_date: prev.start_date, end_date: prev.end_date };
|
|
1477
|
+
const prevResult = await queryService.executeChart(chart, prevParams, getUserContext(request));
|
|
1478
|
+
compareData.push({
|
|
1479
|
+
period: comp.period,
|
|
1480
|
+
label: prevLabel,
|
|
1481
|
+
rows: prevResult.rows
|
|
1482
|
+
});
|
|
1483
|
+
} catch {
|
|
1484
|
+
}
|
|
1485
|
+
}
|
|
1486
|
+
}
|
|
1487
|
+
}
|
|
975
1488
|
reply.header("X-Cache", result.cached ? "HIT" : "MISS");
|
|
976
1489
|
reply.header("X-Query-Duration-Ms", result.durationMs.toFixed(0));
|
|
977
1490
|
const meta = {
|
|
@@ -979,7 +1492,8 @@ async function chartRoutes(fastify, options) {
|
|
|
979
1492
|
durationMs: result.durationMs,
|
|
980
1493
|
rowCount: result.rowCount,
|
|
981
1494
|
cacheKey: result.cacheKey,
|
|
982
|
-
compiledSql: result.compiledSql
|
|
1495
|
+
compiledSql: result.compiledSql,
|
|
1496
|
+
...serverPaginated ? { serverPaginated: true, totalCount } : {}
|
|
983
1497
|
};
|
|
984
1498
|
const response = {
|
|
985
1499
|
columns: result.columns,
|
|
@@ -999,6 +1513,9 @@ async function chartRoutes(fastify, options) {
|
|
|
999
1513
|
if (comparison) {
|
|
1000
1514
|
response.comparison = comparison;
|
|
1001
1515
|
}
|
|
1516
|
+
if (compareData && compareData.length > 0) {
|
|
1517
|
+
response.compareData = compareData;
|
|
1518
|
+
}
|
|
1002
1519
|
let goalData = void 0;
|
|
1003
1520
|
const goals = chart.chart?.goals;
|
|
1004
1521
|
const modelGoal = goals?.find((g) => g.type === "model" && g.source?.model);
|
|
@@ -1056,6 +1573,38 @@ async function chartRoutes(fastify, options) {
|
|
|
1056
1573
|
return reply.status(500).send({ error: message, errorType: classifyError(error2) });
|
|
1057
1574
|
}
|
|
1058
1575
|
});
|
|
1576
|
+
fastify.get("/api/charts/:name/export", async (request, reply) => {
|
|
1577
|
+
const { name } = request.params;
|
|
1578
|
+
const params = { ...request.query };
|
|
1579
|
+
const chart = configLoader.getChartByName(name);
|
|
1580
|
+
if (!chart) {
|
|
1581
|
+
const resp = chartNotFoundResponse(configLoader, name);
|
|
1582
|
+
return reply.status(resp.status).send(resp.body);
|
|
1583
|
+
}
|
|
1584
|
+
const project = configLoader.getProject();
|
|
1585
|
+
const chartExportConfig = chart.chart?.export;
|
|
1586
|
+
if (chartExportConfig?.enabled === false) {
|
|
1587
|
+
return reply.status(403).send({ error: "Export is disabled for this chart", errorType: "validation_error" });
|
|
1588
|
+
}
|
|
1589
|
+
const maxExportRows = chartExportConfig?.max_rows ?? project?.defaults?.table?.max_export_rows ?? 5e5;
|
|
1590
|
+
try {
|
|
1591
|
+
const result = await queryService.executeChartForExport(chart, params, maxExportRows, getUserContext(request));
|
|
1592
|
+
reply.header("Content-Type", "text/csv; charset=utf-8");
|
|
1593
|
+
reply.header("Content-Disposition", `attachment; filename="${name}.csv"`);
|
|
1594
|
+
reply.header("X-Total-Rows", String(result.rowCount));
|
|
1595
|
+
const columns = result.columns.map((c) => c.name);
|
|
1596
|
+
const headerLine = columns.map(escapeCSVField).join(",");
|
|
1597
|
+
const lines = [headerLine];
|
|
1598
|
+
for (const row of result.rows) {
|
|
1599
|
+
const line = columns.map((col) => escapeCSVField(String(row[col] ?? ""))).join(",");
|
|
1600
|
+
lines.push(line);
|
|
1601
|
+
}
|
|
1602
|
+
return reply.send(lines.join("\n"));
|
|
1603
|
+
} catch (error2) {
|
|
1604
|
+
const message = error2 instanceof Error ? error2.message : "Export failed";
|
|
1605
|
+
return reply.status(500).send({ error: message, errorType: classifyError(error2) });
|
|
1606
|
+
}
|
|
1607
|
+
});
|
|
1059
1608
|
fastify.post("/api/charts/batch", async (request, reply) => {
|
|
1060
1609
|
const { charts, includeConfig } = request.body;
|
|
1061
1610
|
if (!charts || !Array.isArray(charts)) {
|
|
@@ -1485,8 +2034,8 @@ async function semanticRoutes(fastify, options) {
|
|
|
1485
2034
|
});
|
|
1486
2035
|
}
|
|
1487
2036
|
const { writeFileSync, mkdirSync } = await import("fs");
|
|
1488
|
-
const { join:
|
|
1489
|
-
const fullPath =
|
|
2037
|
+
const { join: join9, dirname: dirname3 } = await import("path");
|
|
2038
|
+
const fullPath = join9(projectDir, filePath);
|
|
1490
2039
|
mkdirSync(dirname3(fullPath), { recursive: true });
|
|
1491
2040
|
writeFileSync(fullPath, sql, "utf-8");
|
|
1492
2041
|
return { saved: true, path: filePath };
|
|
@@ -1599,7 +2148,7 @@ var SlackNotifier = class {
|
|
|
1599
2148
|
let response = await fetch(webhookUrl, options);
|
|
1600
2149
|
if (!response.ok) {
|
|
1601
2150
|
if (this.retryDelayMs > 0) {
|
|
1602
|
-
await new Promise((
|
|
2151
|
+
await new Promise((resolve3) => setTimeout(resolve3, this.retryDelayMs));
|
|
1603
2152
|
}
|
|
1604
2153
|
response = await fetch(webhookUrl, options);
|
|
1605
2154
|
if (!response.ok) {
|
|
@@ -1858,11 +2407,11 @@ function __rest(s, e) {
|
|
|
1858
2407
|
}
|
|
1859
2408
|
function __awaiter(thisArg, _arguments, P, generator) {
|
|
1860
2409
|
function adopt(value) {
|
|
1861
|
-
return value instanceof P ? value : new P(function(
|
|
1862
|
-
|
|
2410
|
+
return value instanceof P ? value : new P(function(resolve3) {
|
|
2411
|
+
resolve3(value);
|
|
1863
2412
|
});
|
|
1864
2413
|
}
|
|
1865
|
-
return new (P || (P = Promise))(function(
|
|
2414
|
+
return new (P || (P = Promise))(function(resolve3, reject) {
|
|
1866
2415
|
function fulfilled(value) {
|
|
1867
2416
|
try {
|
|
1868
2417
|
step(generator.next(value));
|
|
@@ -1878,7 +2427,7 @@ function __awaiter(thisArg, _arguments, P, generator) {
|
|
|
1878
2427
|
}
|
|
1879
2428
|
}
|
|
1880
2429
|
function step(result) {
|
|
1881
|
-
result.done ?
|
|
2430
|
+
result.done ? resolve3(result.value) : adopt(result.value).then(fulfilled, rejected);
|
|
1882
2431
|
}
|
|
1883
2432
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
1884
2433
|
});
|
|
@@ -4409,15 +4958,15 @@ var RealtimeChannel = class _RealtimeChannel {
|
|
|
4409
4958
|
}
|
|
4410
4959
|
}
|
|
4411
4960
|
} else {
|
|
4412
|
-
return new Promise((
|
|
4961
|
+
return new Promise((resolve3) => {
|
|
4413
4962
|
var _a2, _b2, _c;
|
|
4414
4963
|
const push = this._push(args.type, args, opts.timeout || this.timeout);
|
|
4415
4964
|
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)) {
|
|
4416
|
-
|
|
4965
|
+
resolve3("ok");
|
|
4417
4966
|
}
|
|
4418
|
-
push.receive("ok", () =>
|
|
4419
|
-
push.receive("error", () =>
|
|
4420
|
-
push.receive("timeout", () =>
|
|
4967
|
+
push.receive("ok", () => resolve3("ok"));
|
|
4968
|
+
push.receive("error", () => resolve3("error"));
|
|
4969
|
+
push.receive("timeout", () => resolve3("timed out"));
|
|
4421
4970
|
});
|
|
4422
4971
|
}
|
|
4423
4972
|
}
|
|
@@ -4445,16 +4994,16 @@ var RealtimeChannel = class _RealtimeChannel {
|
|
|
4445
4994
|
};
|
|
4446
4995
|
this.joinPush.destroy();
|
|
4447
4996
|
let leavePush = null;
|
|
4448
|
-
return new Promise((
|
|
4997
|
+
return new Promise((resolve3) => {
|
|
4449
4998
|
leavePush = new Push(this, CHANNEL_EVENTS.leave, {}, timeout);
|
|
4450
4999
|
leavePush.receive("ok", () => {
|
|
4451
5000
|
onClose();
|
|
4452
|
-
|
|
5001
|
+
resolve3("ok");
|
|
4453
5002
|
}).receive("timeout", () => {
|
|
4454
5003
|
onClose();
|
|
4455
|
-
|
|
5004
|
+
resolve3("timed out");
|
|
4456
5005
|
}).receive("error", () => {
|
|
4457
|
-
|
|
5006
|
+
resolve3("error");
|
|
4458
5007
|
});
|
|
4459
5008
|
leavePush.send();
|
|
4460
5009
|
if (!this._canPush()) {
|
|
@@ -4533,8 +5082,8 @@ var RealtimeChannel = class _RealtimeChannel {
|
|
|
4533
5082
|
_trigger(type, payload, ref) {
|
|
4534
5083
|
var _a, _b;
|
|
4535
5084
|
const typeLower = type.toLocaleLowerCase();
|
|
4536
|
-
const { close, error: error2, leave, join:
|
|
4537
|
-
const events = [close, error2, leave,
|
|
5085
|
+
const { close, error: error2, leave, join: join9 } = CHANNEL_EVENTS;
|
|
5086
|
+
const events = [close, error2, leave, join9];
|
|
4538
5087
|
if (ref && events.indexOf(typeLower) >= 0 && ref !== this._joinRef()) {
|
|
4539
5088
|
return;
|
|
4540
5089
|
}
|
|
@@ -6150,7 +6699,7 @@ var _getRequestParams = (method, options, parameters, body) => {
|
|
|
6150
6699
|
return _objectSpread2(_objectSpread2({}, params), parameters);
|
|
6151
6700
|
};
|
|
6152
6701
|
async function _handleRequest(fetcher, method, url, options, parameters, body, namespace) {
|
|
6153
|
-
return new Promise((
|
|
6702
|
+
return new Promise((resolve3, reject) => {
|
|
6154
6703
|
fetcher(url, _getRequestParams(method, options, parameters, body)).then((result) => {
|
|
6155
6704
|
if (!result.ok) throw result;
|
|
6156
6705
|
if (options === null || options === void 0 ? void 0 : options.noResolveJson) return result;
|
|
@@ -6160,7 +6709,7 @@ async function _handleRequest(fetcher, method, url, options, parameters, body, n
|
|
|
6160
6709
|
if (!contentType || !contentType.includes("application/json")) return {};
|
|
6161
6710
|
}
|
|
6162
6711
|
return result.json();
|
|
6163
|
-
}).then((data) =>
|
|
6712
|
+
}).then((data) => resolve3(data)).catch((error2) => handleError(error2, reject, options, namespace));
|
|
6164
6713
|
});
|
|
6165
6714
|
}
|
|
6166
6715
|
function createFetchApi(namespace = "storage") {
|
|
@@ -13914,6 +14463,12 @@ async function publicRoutes(fastify, options) {
|
|
|
13914
14463
|
}
|
|
13915
14464
|
|
|
13916
14465
|
// ../server/dist/routes/chat.js
|
|
14466
|
+
import { resolve, join as join6 } from "path";
|
|
14467
|
+
import { readFile as readFile3 } from "fs/promises";
|
|
14468
|
+
function isChatEnabled(configLoader) {
|
|
14469
|
+
const project = configLoader.getProject();
|
|
14470
|
+
return !!(project?.features?.chat && process.env.ANTHROPIC_API_KEY);
|
|
14471
|
+
}
|
|
13917
14472
|
async function chatRoutes(fastify, options) {
|
|
13918
14473
|
const { configLoader, queryService } = options;
|
|
13919
14474
|
fastify.post("/api/chat/generate-text", async (request, reply) => {
|
|
@@ -14036,7 +14591,7 @@ async function chatRoutes(fastify, options) {
|
|
|
14036
14591
|
systemParts.push("Output ONLY the new text to insert. Do NOT repeat the existing content.");
|
|
14037
14592
|
}
|
|
14038
14593
|
const systemPrompt = systemParts.join("\n");
|
|
14039
|
-
const { AnthropicProvider } = await import("./dist-
|
|
14594
|
+
const { AnthropicProvider } = await import("./dist-7CRX2GIR.js");
|
|
14040
14595
|
const provider = new AnthropicProvider(apiKey);
|
|
14041
14596
|
const response = await provider.chat({
|
|
14042
14597
|
system: systemPrompt,
|
|
@@ -14052,6 +14607,210 @@ async function chatRoutes(fastify, options) {
|
|
|
14052
14607
|
return reply.status(500).send({ error: message });
|
|
14053
14608
|
}
|
|
14054
14609
|
});
|
|
14610
|
+
fastify.get("/api/chat/agents", async (request, reply) => {
|
|
14611
|
+
if (!isChatEnabled(configLoader)) {
|
|
14612
|
+
return reply.code(404).send({ error: "Chat not enabled" });
|
|
14613
|
+
}
|
|
14614
|
+
const registry = configLoader.getAgentRegistry();
|
|
14615
|
+
const agents = registry.listAgents().map((a) => ({
|
|
14616
|
+
name: a.name,
|
|
14617
|
+
title: a.title,
|
|
14618
|
+
description: a.description,
|
|
14619
|
+
builtIn: a.builtIn
|
|
14620
|
+
}));
|
|
14621
|
+
return agents;
|
|
14622
|
+
});
|
|
14623
|
+
fastify.post("/api/chat/stream", async (request, reply) => {
|
|
14624
|
+
if (!isChatEnabled(configLoader)) {
|
|
14625
|
+
return reply.code(404).send({ error: "Chat not enabled" });
|
|
14626
|
+
}
|
|
14627
|
+
const body = request.body;
|
|
14628
|
+
if (!body.message) {
|
|
14629
|
+
return reply.code(400).send({ error: "message is required" });
|
|
14630
|
+
}
|
|
14631
|
+
const apiKey = process.env.ANTHROPIC_API_KEY;
|
|
14632
|
+
const registry = configLoader.getAgentRegistry();
|
|
14633
|
+
const router = new RouterAgent(registry);
|
|
14634
|
+
const chatContext = body.context ?? { type: "general" };
|
|
14635
|
+
const agentConfig = router.selectAgent(body.message, chatContext, body.agent);
|
|
14636
|
+
reply.raw.writeHead(200, {
|
|
14637
|
+
"Content-Type": "text/event-stream",
|
|
14638
|
+
"Cache-Control": "no-cache",
|
|
14639
|
+
Connection: "keep-alive"
|
|
14640
|
+
});
|
|
14641
|
+
const emitEvent = (event) => {
|
|
14642
|
+
reply.raw.write(formatSSEEvent(event));
|
|
14643
|
+
};
|
|
14644
|
+
emitEvent({ type: "agent_selected", data: { name: agentConfig.name, title: agentConfig.title } });
|
|
14645
|
+
const project = configLoader.getProject();
|
|
14646
|
+
const toolContext = {
|
|
14647
|
+
configLoader: {
|
|
14648
|
+
getChartByName: (name) => configLoader.getChartByName(name),
|
|
14649
|
+
getDashboardByName: (name) => configLoader.getDashboardByName(name),
|
|
14650
|
+
getDashboards: () => configLoader.getDashboards(),
|
|
14651
|
+
getCharts: () => configLoader.getCharts(),
|
|
14652
|
+
getModels: () => configLoader.getModels?.() ?? [],
|
|
14653
|
+
getProject: () => configLoader.getProject(),
|
|
14654
|
+
load: () => configLoader.load()
|
|
14655
|
+
},
|
|
14656
|
+
queryService: {
|
|
14657
|
+
executeChart: (chart, params, userCtx) => queryService.executeChart(chart, params, userCtx),
|
|
14658
|
+
executeRawSql: (sql) => queryService.executeRawSql(sql)
|
|
14659
|
+
},
|
|
14660
|
+
projectDir: options.projectDir,
|
|
14661
|
+
catalogPath: resolve(options.projectDir, ".yamchart", "catalog.json"),
|
|
14662
|
+
agentRegistry: configLoader.getAgentRegistry(),
|
|
14663
|
+
userContext: request.user ? { id: request.user.id, email: request.user.email, role: request.user.role } : void 0,
|
|
14664
|
+
emitEvent,
|
|
14665
|
+
chatContext,
|
|
14666
|
+
catalogFilter: project?.catalog?.include
|
|
14667
|
+
};
|
|
14668
|
+
let systemPrompt = agentConfig.systemPrompt;
|
|
14669
|
+
if (chatContext.type === "dashboard" && chatContext.name) {
|
|
14670
|
+
const dashboard = configLoader.getDashboardByName(chatContext.name);
|
|
14671
|
+
if (dashboard) {
|
|
14672
|
+
const chartRefs = [];
|
|
14673
|
+
const extractRefs = (layout) => {
|
|
14674
|
+
for (const row of layout.rows ?? []) {
|
|
14675
|
+
for (const widget of row.widgets ?? []) {
|
|
14676
|
+
if (widget.type === "chart" && widget.ref)
|
|
14677
|
+
chartRefs.push(widget.ref);
|
|
14678
|
+
}
|
|
14679
|
+
}
|
|
14680
|
+
};
|
|
14681
|
+
if (dashboard.layout)
|
|
14682
|
+
extractRefs(dashboard.layout);
|
|
14683
|
+
if (dashboard.tabs) {
|
|
14684
|
+
for (const tab of dashboard.tabs) {
|
|
14685
|
+
if (tab.layout)
|
|
14686
|
+
extractRefs(tab.layout);
|
|
14687
|
+
}
|
|
14688
|
+
}
|
|
14689
|
+
const chartDetails = [];
|
|
14690
|
+
for (const ref of chartRefs) {
|
|
14691
|
+
const chart = configLoader.getChartByName(ref);
|
|
14692
|
+
if (!chart)
|
|
14693
|
+
continue;
|
|
14694
|
+
const model = chart.source?.model ? `, model: "${chart.source.model}"` : "";
|
|
14695
|
+
const params = chart.parameters?.length ? `, params: [${chart.parameters.map((p) => `${p.name}(${p.type})`).join(", ")}]` : "";
|
|
14696
|
+
chartDetails.push(` - "${ref}" (${chart.chart?.type ?? "unknown"}${model}${params}): ${chart.title ?? ref}`);
|
|
14697
|
+
}
|
|
14698
|
+
const filters = dashboard.filters?.length ? `
|
|
14699
|
+
Dashboard filters: ${dashboard.filters.map((f) => `${f.name}(${f.type}, default: ${f.default ?? "none"})`).join(", ")}` : "";
|
|
14700
|
+
systemPrompt += `
|
|
14701
|
+
|
|
14702
|
+
The user is viewing the "${dashboard.title ?? dashboard.name}" dashboard (id: "${dashboard.name}").${filters}
|
|
14703
|
+
Charts on this dashboard:
|
|
14704
|
+
${chartDetails.join("\n")}
|
|
14705
|
+
|
|
14706
|
+
IMPORTANT: When using query_model, pass the chart name (e.g. "${chartRefs[0] ?? "chart-name"}") \u2014 NOT the model name. The chart name is what appears before the parentheses above.`;
|
|
14707
|
+
}
|
|
14708
|
+
} else if (chatContext.type === "chart" && chatContext.name) {
|
|
14709
|
+
const chart = configLoader.getChartByName(chatContext.name);
|
|
14710
|
+
if (chart) {
|
|
14711
|
+
const model = chart.source?.model ? ` Model: "${chart.source.model}".` : "";
|
|
14712
|
+
const params = chart.parameters?.length ? ` Parameters: ${chart.parameters.map((p) => `${p.name}(${p.type})`).join(", ")}.` : "";
|
|
14713
|
+
systemPrompt += `
|
|
14714
|
+
|
|
14715
|
+
The user is viewing the "${chart.title ?? chart.name}" chart (id: "${chart.name}", type: ${chart.chart?.type}).${model}${params}`;
|
|
14716
|
+
}
|
|
14717
|
+
} else if (chatContext.type === "explore") {
|
|
14718
|
+
const catalogPath = join6(options.projectDir, ".yamchart", "catalog.json");
|
|
14719
|
+
const tableLines = [];
|
|
14720
|
+
try {
|
|
14721
|
+
const catalogData = JSON.parse(await readFile3(catalogPath, "utf-8"));
|
|
14722
|
+
const filteredModels = filterCatalogEntries(catalogData.models ?? [], project?.catalog?.include);
|
|
14723
|
+
for (const entry of filteredModels) {
|
|
14724
|
+
const fullName = entry.table ?? entry.name;
|
|
14725
|
+
const cols = (entry.columns ?? []).map((c) => c.name).join(", ");
|
|
14726
|
+
const colSuffix = cols ? ` (${entry.columns?.length} columns: ${cols})` : "";
|
|
14727
|
+
tableLines.push(`- ${fullName}${colSuffix}`);
|
|
14728
|
+
}
|
|
14729
|
+
} catch {
|
|
14730
|
+
}
|
|
14731
|
+
const configModels = configLoader.getModels?.() ?? [];
|
|
14732
|
+
const modelLines = configModels.map((m) => m.metadata.description ? `- ${m.metadata.name}: ${m.metadata.description}` : `- ${m.metadata.name}`);
|
|
14733
|
+
if (tableLines.length === 0 && modelLines.length === 0) {
|
|
14734
|
+
systemPrompt += "\n\nNo catalog data available. Use get_catalog_tables or run_sql to discover tables.";
|
|
14735
|
+
} else {
|
|
14736
|
+
const parts = ["\n"];
|
|
14737
|
+
if (tableLines.length > 0) {
|
|
14738
|
+
parts.push("## Available Tables", ...tableLines);
|
|
14739
|
+
}
|
|
14740
|
+
if (modelLines.length > 0) {
|
|
14741
|
+
if (parts.length > 1)
|
|
14742
|
+
parts.push("");
|
|
14743
|
+
parts.push("## Available Models", ...modelLines);
|
|
14744
|
+
}
|
|
14745
|
+
systemPrompt += parts.join("\n");
|
|
14746
|
+
}
|
|
14747
|
+
}
|
|
14748
|
+
const messages = [];
|
|
14749
|
+
for (const msg of body.history ?? []) {
|
|
14750
|
+
messages.push({ role: msg.role, content: msg.content });
|
|
14751
|
+
}
|
|
14752
|
+
messages.push({ role: "user", content: body.message });
|
|
14753
|
+
try {
|
|
14754
|
+
const agent = new StreamingChatAgent(agentConfig, apiKey);
|
|
14755
|
+
await agent.run(systemPrompt, messages, toolContext, emitEvent);
|
|
14756
|
+
} catch (err) {
|
|
14757
|
+
emitEvent({
|
|
14758
|
+
type: "error",
|
|
14759
|
+
data: { message: err instanceof Error ? err.message : "Stream failed" }
|
|
14760
|
+
});
|
|
14761
|
+
}
|
|
14762
|
+
reply.raw.end();
|
|
14763
|
+
});
|
|
14764
|
+
}
|
|
14765
|
+
|
|
14766
|
+
// ../server/dist/routes/catalog.js
|
|
14767
|
+
import { join as join7 } from "path";
|
|
14768
|
+
import { readFile as readFile4 } from "fs/promises";
|
|
14769
|
+
async function catalogRoutes(fastify, options) {
|
|
14770
|
+
const { configLoader, projectDir } = options;
|
|
14771
|
+
fastify.get("/api/catalog/summary", async (_request2, reply) => {
|
|
14772
|
+
const connections = configLoader.getConnections().map((c) => ({
|
|
14773
|
+
name: c.name,
|
|
14774
|
+
type: c.type
|
|
14775
|
+
}));
|
|
14776
|
+
const configModels = configLoader.getModels().map((m) => ({
|
|
14777
|
+
name: m.metadata.name,
|
|
14778
|
+
description: m.metadata.description
|
|
14779
|
+
}));
|
|
14780
|
+
const catalogPath = join7(projectDir, ".yamchart", "catalog.json");
|
|
14781
|
+
let tables = [];
|
|
14782
|
+
try {
|
|
14783
|
+
const catalogData = JSON.parse(await readFile4(catalogPath, "utf-8"));
|
|
14784
|
+
const allCatalogModels = catalogData.models ?? [];
|
|
14785
|
+
const project = configLoader.getProject();
|
|
14786
|
+
const catalogModels = filterCatalogEntries(allCatalogModels, project?.catalog?.include);
|
|
14787
|
+
tables = catalogModels.map((m) => {
|
|
14788
|
+
const fullName = m.table ?? m.name ?? "";
|
|
14789
|
+
const parts = fullName.split(".");
|
|
14790
|
+
const tableName = parts[parts.length - 1] ?? fullName;
|
|
14791
|
+
const schema = parts.length > 1 ? parts.slice(0, -1).join(".") : void 0;
|
|
14792
|
+
const columns = (m.columns ?? []).map((c) => ({
|
|
14793
|
+
name: c.name,
|
|
14794
|
+
type: c.data_type
|
|
14795
|
+
}));
|
|
14796
|
+
return {
|
|
14797
|
+
name: tableName,
|
|
14798
|
+
...schema ? { schema } : {},
|
|
14799
|
+
connection: m.connection ?? (connections[0]?.name ?? "default"),
|
|
14800
|
+
columnCount: columns.length,
|
|
14801
|
+
columns
|
|
14802
|
+
};
|
|
14803
|
+
});
|
|
14804
|
+
} catch {
|
|
14805
|
+
}
|
|
14806
|
+
return reply.send({
|
|
14807
|
+
connections,
|
|
14808
|
+
tables,
|
|
14809
|
+
models: configModels,
|
|
14810
|
+
tableCount: tables.length,
|
|
14811
|
+
modelCount: configModels.length
|
|
14812
|
+
});
|
|
14813
|
+
});
|
|
14055
14814
|
}
|
|
14056
14815
|
|
|
14057
14816
|
// ../server/dist/routes/events.js
|
|
@@ -14093,18 +14852,18 @@ data: ${data}
|
|
|
14093
14852
|
}
|
|
14094
14853
|
|
|
14095
14854
|
// ../server/dist/server.js
|
|
14096
|
-
import { join as
|
|
14855
|
+
import { join as join8, dirname as dirname2 } from "path";
|
|
14097
14856
|
import { fileURLToPath } from "url";
|
|
14098
|
-
import { access as access2, readFile as
|
|
14857
|
+
import { access as access2, readFile as readFile5 } from "fs/promises";
|
|
14099
14858
|
import { watch as fsWatch } from "fs";
|
|
14100
14859
|
var __dirname = dirname2(fileURLToPath(import.meta.url));
|
|
14101
|
-
var packageJsonPath =
|
|
14102
|
-
var packageJson = JSON.parse(await
|
|
14860
|
+
var packageJsonPath = join8(__dirname, "..", "package.json");
|
|
14861
|
+
var packageJson = JSON.parse(await readFile5(packageJsonPath, "utf-8"));
|
|
14103
14862
|
var VERSION = packageJson.version;
|
|
14104
14863
|
async function enrichRefsFromCatalog(projectDir, refs) {
|
|
14105
14864
|
try {
|
|
14106
|
-
const catalogPath =
|
|
14107
|
-
const catalogData = JSON.parse(await
|
|
14865
|
+
const catalogPath = join8(projectDir, ".yamchart", "catalog.json");
|
|
14866
|
+
const catalogData = JSON.parse(await readFile5(catalogPath, "utf-8"));
|
|
14108
14867
|
for (const model of catalogData.models ?? []) {
|
|
14109
14868
|
if (model.name && model.table) {
|
|
14110
14869
|
refs[model.name] = model.table;
|
|
@@ -14114,7 +14873,7 @@ async function enrichRefsFromCatalog(projectDir, refs) {
|
|
|
14114
14873
|
}
|
|
14115
14874
|
}
|
|
14116
14875
|
async function createServer(options) {
|
|
14117
|
-
const { projectDir, port = 3001, host = "0.0.0.0", watch: watch2 = false, serveStatic = process.env.NODE_ENV === "production", staticDir =
|
|
14876
|
+
const { projectDir, port = 3001, host = "0.0.0.0", watch: watch2 = false, serveStatic = process.env.NODE_ENV === "production", staticDir = join8(__dirname, "public"), auth } = options;
|
|
14118
14877
|
if (auth) {
|
|
14119
14878
|
initAuthServer(auth.supabaseUrl, auth.supabaseServiceKey);
|
|
14120
14879
|
}
|
|
@@ -14168,7 +14927,8 @@ async function createServer(options) {
|
|
|
14168
14927
|
connector,
|
|
14169
14928
|
cache,
|
|
14170
14929
|
models,
|
|
14171
|
-
refs
|
|
14930
|
+
refs,
|
|
14931
|
+
dialect: defaultConnection.type
|
|
14172
14932
|
});
|
|
14173
14933
|
const semanticService = new SemanticService({
|
|
14174
14934
|
projectDir,
|
|
@@ -14248,6 +15008,7 @@ async function createServer(options) {
|
|
|
14248
15008
|
await protectedRoutes.register(chartEventsRoutes, { configLoader, projectDir });
|
|
14249
15009
|
await protectedRoutes.register(dashboardRoutes, { configLoader, gitService, projectDir, queryService });
|
|
14250
15010
|
await protectedRoutes.register(chatRoutes, { configLoader, queryService, projectDir });
|
|
15011
|
+
await protectedRoutes.register(catalogRoutes, { configLoader, projectDir });
|
|
14251
15012
|
});
|
|
14252
15013
|
} else if (authDb) {
|
|
14253
15014
|
const localMiddleware = createLocalAuthMiddleware(authDb);
|
|
@@ -14258,6 +15019,7 @@ async function createServer(options) {
|
|
|
14258
15019
|
await protectedRoutes.register(chartEventsRoutes, { configLoader, projectDir });
|
|
14259
15020
|
await protectedRoutes.register(dashboardRoutes, { configLoader, gitService, projectDir, queryService });
|
|
14260
15021
|
await protectedRoutes.register(chatRoutes, { configLoader, queryService, projectDir });
|
|
15022
|
+
await protectedRoutes.register(catalogRoutes, { configLoader, projectDir });
|
|
14261
15023
|
await protectedRoutes.register(sharesRoutes, { authDb });
|
|
14262
15024
|
});
|
|
14263
15025
|
} else {
|
|
@@ -14266,8 +15028,9 @@ async function createServer(options) {
|
|
|
14266
15028
|
await fastify.register(chartEventsRoutes, { configLoader, projectDir });
|
|
14267
15029
|
await fastify.register(dashboardRoutes, { configLoader, gitService, projectDir, queryService });
|
|
14268
15030
|
await fastify.register(chatRoutes, { configLoader, queryService, projectDir });
|
|
15031
|
+
await fastify.register(catalogRoutes, { configLoader, projectDir });
|
|
14269
15032
|
}
|
|
14270
|
-
const assetsDir =
|
|
15033
|
+
const assetsDir = join8(projectDir, "assets");
|
|
14271
15034
|
try {
|
|
14272
15035
|
await access2(assetsDir);
|
|
14273
15036
|
await fastify.register(fastifyStatic, {
|
|
@@ -14278,10 +15041,10 @@ async function createServer(options) {
|
|
|
14278
15041
|
} catch {
|
|
14279
15042
|
}
|
|
14280
15043
|
if (project.theme?.customCss) {
|
|
14281
|
-
const cssPath =
|
|
15044
|
+
const cssPath = join8(projectDir, project.theme.customCss);
|
|
14282
15045
|
fastify.get("/api/theme/custom.css", async (_request2, reply) => {
|
|
14283
15046
|
try {
|
|
14284
|
-
const content = await
|
|
15047
|
+
const content = await readFile5(cssPath, "utf-8");
|
|
14285
15048
|
return reply.type("text/css").send(content);
|
|
14286
15049
|
} catch {
|
|
14287
15050
|
return reply.status(404).send("");
|
|
@@ -14337,7 +15100,7 @@ async function createServer(options) {
|
|
|
14337
15100
|
fastify.log.info(`Semantic model rebuilt: ${semanticService.getModel()?.entities.length ?? 0} entities`);
|
|
14338
15101
|
broadcastConfigChange();
|
|
14339
15102
|
});
|
|
14340
|
-
const catalogPath =
|
|
15103
|
+
const catalogPath = join8(projectDir, ".yamchart", "catalog.json");
|
|
14341
15104
|
try {
|
|
14342
15105
|
catalogWatcher = fsWatch(catalogPath, () => {
|
|
14343
15106
|
semanticService.buildFromDisk();
|
|
@@ -14402,15 +15165,15 @@ async function runDevServer(projectDir, options) {
|
|
|
14402
15165
|
let localAuth;
|
|
14403
15166
|
try {
|
|
14404
15167
|
const { readFileSync: readFileSync2 } = await import("fs");
|
|
14405
|
-
const { parse } = await import("yaml");
|
|
14406
|
-
const { resolveProjectConfig: resolveProjectConfig2, deepMerge: deepMerge3 } = await import("./dist-
|
|
14407
|
-
const raw = readFileSync2(
|
|
14408
|
-
const rawConfig =
|
|
15168
|
+
const { parse: parse2 } = await import("yaml");
|
|
15169
|
+
const { resolveProjectConfig: resolveProjectConfig2, deepMerge: deepMerge3 } = await import("./dist-4GUE24QV.js");
|
|
15170
|
+
const raw = readFileSync2(resolve2(projectDir, "yamchart.yaml"), "utf-8");
|
|
15171
|
+
const rawConfig = parse2(raw);
|
|
14409
15172
|
const resolvedEnv = options.env || process.env.YAMCHART_ENV || void 0;
|
|
14410
15173
|
let projectConfig = resolveProjectConfig2(rawConfig, resolvedEnv);
|
|
14411
15174
|
try {
|
|
14412
|
-
const localRaw = readFileSync2(
|
|
14413
|
-
const localOverrides =
|
|
15175
|
+
const localRaw = readFileSync2(resolve2(projectDir, "yamchart.local.yaml"), "utf-8");
|
|
15176
|
+
const localOverrides = parse2(localRaw);
|
|
14414
15177
|
if (localOverrides && typeof localOverrides === "object") {
|
|
14415
15178
|
projectConfig = deepMerge3(projectConfig, localOverrides);
|
|
14416
15179
|
}
|
|
@@ -14419,16 +15182,16 @@ async function runDevServer(projectDir, options) {
|
|
|
14419
15182
|
if (projectConfig?.auth?.enabled) {
|
|
14420
15183
|
localAuth = {
|
|
14421
15184
|
enabled: true,
|
|
14422
|
-
dbPath: projectConfig.auth.db_path ?
|
|
15185
|
+
dbPath: projectConfig.auth.db_path ? resolve2(projectConfig.auth.db_path.replace(/^~/, homedir())) : resolve2(homedir(), ".yamchart", "auth.db"),
|
|
14423
15186
|
sessionTtlMs: projectConfig.auth.session_ttl ? parseTtl(projectConfig.auth.session_ttl) : 30 * 24 * 60 * 60 * 1e3
|
|
14424
15187
|
};
|
|
14425
15188
|
}
|
|
14426
|
-
const connectionDir =
|
|
15189
|
+
const connectionDir = resolve2(projectDir, "connections");
|
|
14427
15190
|
const targetConn = process.env.YAMCHART_CONNECTION || projectConfig?.defaults?.connection;
|
|
14428
15191
|
if (targetConn) {
|
|
14429
15192
|
try {
|
|
14430
|
-
const connRaw = readFileSync2(
|
|
14431
|
-
const connConfig =
|
|
15193
|
+
const connRaw = readFileSync2(resolve2(connectionDir, `${targetConn}.yaml`), "utf-8");
|
|
15194
|
+
const connConfig = parse2(connRaw);
|
|
14432
15195
|
if (connConfig?.auth?.type === "externalbrowser") {
|
|
14433
15196
|
usesBrowserAuth = true;
|
|
14434
15197
|
}
|
|
@@ -14443,21 +15206,33 @@ async function runDevServer(projectDir, options) {
|
|
|
14443
15206
|
}
|
|
14444
15207
|
const spinner2 = spinner("Starting server...");
|
|
14445
15208
|
let server;
|
|
14446
|
-
|
|
14447
|
-
|
|
14448
|
-
|
|
14449
|
-
|
|
14450
|
-
|
|
14451
|
-
|
|
14452
|
-
|
|
14453
|
-
|
|
14454
|
-
|
|
14455
|
-
|
|
14456
|
-
|
|
14457
|
-
|
|
14458
|
-
|
|
14459
|
-
|
|
14460
|
-
|
|
15209
|
+
let actualPort = options.port;
|
|
15210
|
+
const MAX_PORT_ATTEMPTS = 10;
|
|
15211
|
+
for (let attempt = 0; attempt < MAX_PORT_ATTEMPTS; attempt++) {
|
|
15212
|
+
try {
|
|
15213
|
+
server = await createServer({
|
|
15214
|
+
projectDir,
|
|
15215
|
+
port: actualPort,
|
|
15216
|
+
watch: true,
|
|
15217
|
+
serveStatic: !options.apiOnly,
|
|
15218
|
+
localAuth,
|
|
15219
|
+
env: options.env
|
|
15220
|
+
});
|
|
15221
|
+
await server.start();
|
|
15222
|
+
spinner2.stop();
|
|
15223
|
+
if (actualPort !== options.port) {
|
|
15224
|
+
info(`Port ${options.port} is in use, starting on port ${actualPort}`);
|
|
15225
|
+
}
|
|
15226
|
+
break;
|
|
15227
|
+
} catch (err) {
|
|
15228
|
+
if (err && typeof err === "object" && "code" in err && err.code === "EADDRINUSE" && attempt < MAX_PORT_ATTEMPTS - 1) {
|
|
15229
|
+
actualPort++;
|
|
15230
|
+
continue;
|
|
15231
|
+
}
|
|
15232
|
+
spinner2.fail("Failed to start server");
|
|
15233
|
+
error(err instanceof Error ? err.message : "Unknown error");
|
|
15234
|
+
process.exit(1);
|
|
15235
|
+
}
|
|
14461
15236
|
}
|
|
14462
15237
|
const project = server.configLoader.getProject();
|
|
14463
15238
|
const charts = server.configLoader.getCharts();
|
|
@@ -14466,8 +15241,8 @@ async function runDevServer(projectDir, options) {
|
|
|
14466
15241
|
box([
|
|
14467
15242
|
`Yamchart v${options.version}`,
|
|
14468
15243
|
``,
|
|
14469
|
-
`Dashboard: http://localhost:${
|
|
14470
|
-
`API: http://localhost:${
|
|
15244
|
+
`Dashboard: http://localhost:${actualPort}`,
|
|
15245
|
+
`API: http://localhost:${actualPort}/api`,
|
|
14471
15246
|
``,
|
|
14472
15247
|
`Project: ${project.name}`,
|
|
14473
15248
|
`Charts: ${charts.length} loaded`,
|
|
@@ -14478,7 +15253,7 @@ async function runDevServer(projectDir, options) {
|
|
|
14478
15253
|
]);
|
|
14479
15254
|
newline();
|
|
14480
15255
|
if (options.open && !options.apiOnly) {
|
|
14481
|
-
const url = `http://localhost:${
|
|
15256
|
+
const url = `http://localhost:${actualPort}`;
|
|
14482
15257
|
const { exec } = await import("child_process");
|
|
14483
15258
|
const command = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
|
|
14484
15259
|
exec(`${command} ${url}`);
|
|
@@ -14495,4 +15270,4 @@ async function runDevServer(projectDir, options) {
|
|
|
14495
15270
|
export {
|
|
14496
15271
|
runDevServer
|
|
14497
15272
|
};
|
|
14498
|
-
//# sourceMappingURL=dev-
|
|
15273
|
+
//# sourceMappingURL=dev-6QGAB4ZH.js.map
|