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.
Files changed (65) hide show
  1. package/dist/{advisor-54JBE2EV.js → advisor-Z7TKPPBR.js} +10 -10
  2. package/dist/agent-KWKPAYT2.js +8 -0
  3. package/dist/{chunk-NCPWAWIM.js → chunk-AMHCOB4D.js} +4 -4
  4. package/dist/{chunk-5N3FYFBV.js → chunk-CWAWATL4.js} +34 -7
  5. package/dist/chunk-CWAWATL4.js.map +1 -0
  6. package/dist/{chunk-C7A7TKSY.js → chunk-E2QN2M7S.js} +77 -7
  7. package/dist/chunk-E2QN2M7S.js.map +1 -0
  8. package/dist/{chunk-YMQ4PWVJ.js → chunk-FZFBBB7K.js} +2 -2
  9. package/dist/{chunk-GLCEDWGH.js → chunk-G57J2WQM.js} +4 -4
  10. package/dist/chunk-ZA6AOQVZ.js +677 -0
  11. package/dist/chunk-ZA6AOQVZ.js.map +1 -0
  12. package/dist/{connection-utils-MXEF6X7K.js → connection-utils-CTPN7PV3.js} +4 -4
  13. package/dist/{describe-XKLBZEWG.js → describe-4NME6RCB.js} +5 -5
  14. package/dist/{dev-Z2R2DBWO.js → dev-6QGAB4ZH.js} +845 -70
  15. package/dist/dev-6QGAB4ZH.js.map +1 -0
  16. package/dist/{dist-LJR7TAW4.js → dist-4GUE24QV.js} +4 -2
  17. package/dist/{dist-GVNWQXFR.js → dist-7CRX2GIR.js} +2 -2
  18. package/dist/{dist-E2PVGIPT.js → dist-VNX77VV5.js} +4 -2
  19. package/dist/index.js +21 -21
  20. package/dist/public/assets/{EventManagement-DtPDwZ-w.js → EventManagement-MMsAkJKj.js} +2 -2
  21. package/dist/public/assets/ExplorePage-BSkSNgLT.js +1 -0
  22. package/dist/public/assets/{LoginPage-DBq1qDOK.js → LoginPage-vaI1dnyL.js} +1 -1
  23. package/dist/public/assets/PublicViewer-B-OKj2cg.js +1 -0
  24. package/dist/public/assets/{SetupWizard-hgd12cdr.js → SetupWizard-DvlVX2O6.js} +1 -1
  25. package/dist/public/assets/{ShareManagement-D82oEJJg.js → ShareManagement-ulvPrOAQ.js} +1 -1
  26. package/dist/public/assets/{UserManagement-CZsxY9aP.js → UserManagement-CvmpNy3o.js} +1 -1
  27. package/dist/public/assets/{index-B_fusLA_.css → index-CfyF2Wf-.css} +1 -1
  28. package/dist/public/assets/index-DD59fsOk.js +195 -0
  29. package/dist/public/assets/{index.es-BmKO-vE1.js → index.es-BeTaRWIv.js} +1 -1
  30. package/dist/public/assets/{jspdf.es.min-DMVrmE3G.js → jspdf.es.min-9haD1GSE.js} +3 -3
  31. package/dist/public/index.html +2 -2
  32. package/dist/{query-MXMFI5TB.js → query-Z75RKTHV.js} +4 -4
  33. package/dist/{sample-HDPYNAKS.js → sample-OIJNXQNC.js} +4 -4
  34. package/dist/{search-6CPEPJTI.js → search-YDCPIDZX.js} +5 -5
  35. package/dist/{source-resolver-PCASPRSD.js → source-resolver-4SUWXUGW.js} +5 -5
  36. package/dist/source-resolver-4SUWXUGW.js.map +1 -0
  37. package/dist/{sync-warehouse-XC7YYZKC.js → sync-warehouse-NZFDS6WK.js} +4 -4
  38. package/dist/{tables-26PNVZIC.js → tables-WJS2VI4L.js} +5 -5
  39. package/dist/templates/default/CLAUDE.md +1 -1
  40. package/dist/templates/default/docs/yamchart-reference.md +164 -2
  41. package/dist/templates/default/yamchart.yaml +3 -0
  42. package/dist/{test-APA44AIF.js → test-I4XOF7TZ.js} +5 -17
  43. package/dist/test-I4XOF7TZ.js.map +1 -0
  44. package/package.json +3 -2
  45. package/dist/chunk-5N3FYFBV.js.map +0 -1
  46. package/dist/chunk-C7A7TKSY.js.map +0 -1
  47. package/dist/dev-Z2R2DBWO.js.map +0 -1
  48. package/dist/public/assets/PublicViewer-Da8Cu1C1.js +0 -1
  49. package/dist/public/assets/index-DVSm0iiw.js +0 -187
  50. package/dist/test-APA44AIF.js.map +0 -1
  51. /package/dist/{advisor-54JBE2EV.js.map → advisor-Z7TKPPBR.js.map} +0 -0
  52. /package/dist/{connection-utils-MXEF6X7K.js.map → agent-KWKPAYT2.js.map} +0 -0
  53. /package/dist/{chunk-NCPWAWIM.js.map → chunk-AMHCOB4D.js.map} +0 -0
  54. /package/dist/{chunk-YMQ4PWVJ.js.map → chunk-FZFBBB7K.js.map} +0 -0
  55. /package/dist/{chunk-GLCEDWGH.js.map → chunk-G57J2WQM.js.map} +0 -0
  56. /package/dist/{dist-E2PVGIPT.js.map → connection-utils-CTPN7PV3.js.map} +0 -0
  57. /package/dist/{describe-XKLBZEWG.js.map → describe-4NME6RCB.js.map} +0 -0
  58. /package/dist/{dist-LJR7TAW4.js.map → dist-4GUE24QV.js.map} +0 -0
  59. /package/dist/{dist-GVNWQXFR.js.map → dist-7CRX2GIR.js.map} +0 -0
  60. /package/dist/{source-resolver-PCASPRSD.js.map → dist-VNX77VV5.js.map} +0 -0
  61. /package/dist/{query-MXMFI5TB.js.map → query-Z75RKTHV.js.map} +0 -0
  62. /package/dist/{sample-HDPYNAKS.js.map → sample-OIJNXQNC.js.map} +0 -0
  63. /package/dist/{search-6CPEPJTI.js.map → search-YDCPIDZX.js.map} +0 -0
  64. /package/dist/{sync-warehouse-XC7YYZKC.js.map → sync-warehouse-NZFDS6WK.js.map} +0 -0
  65. /package/dist/{tables-26PNVZIC.js.map → tables-WJS2VI4L.js.map} +0 -0
@@ -4,9 +4,9 @@ import {
4
4
  import {
5
5
  createConnector,
6
6
  resolveConnection
7
- } from "./chunk-NCPWAWIM.js";
8
- import "./chunk-C7A7TKSY.js";
9
- import "./chunk-5N3FYFBV.js";
7
+ } from "./chunk-AMHCOB4D.js";
8
+ import "./chunk-E2QN2M7S.js";
9
+ import "./chunk-CWAWATL4.js";
10
10
  import "./chunk-UND73EOB.js";
11
11
  import "./chunk-DGUM43GV.js";
12
12
 
@@ -38,4 +38,4 @@ async function sampleTable(projectDir, table, options) {
38
38
  export {
39
39
  sampleTable
40
40
  };
41
- //# sourceMappingURL=sample-HDPYNAKS.js.map
41
+ //# sourceMappingURL=sample-OIJNXQNC.js.map
@@ -1,11 +1,11 @@
1
1
  import {
2
2
  resolveSearchSource
3
- } from "./chunk-YMQ4PWVJ.js";
3
+ } from "./chunk-FZFBBB7K.js";
4
4
  import "./chunk-VJC24RKT.js";
5
5
  import "./chunk-EHM6AMMA.js";
6
- import "./chunk-NCPWAWIM.js";
7
- import "./chunk-C7A7TKSY.js";
8
- import "./chunk-5N3FYFBV.js";
6
+ import "./chunk-AMHCOB4D.js";
7
+ import "./chunk-E2QN2M7S.js";
8
+ import "./chunk-CWAWATL4.js";
9
9
  import "./chunk-UND73EOB.js";
10
10
  import "./chunk-DGUM43GV.js";
11
11
 
@@ -25,4 +25,4 @@ async function searchDatabase(projectDir, keyword, options) {
25
25
  export {
26
26
  searchDatabase
27
27
  };
28
- //# sourceMappingURL=search-6CPEPJTI.js.map
28
+ //# sourceMappingURL=search-YDCPIDZX.js.map
@@ -2,12 +2,12 @@ import {
2
2
  resolveDescribeSource,
3
3
  resolveSearchSource,
4
4
  resolveTablesSource
5
- } from "./chunk-YMQ4PWVJ.js";
5
+ } from "./chunk-FZFBBB7K.js";
6
6
  import "./chunk-VJC24RKT.js";
7
7
  import "./chunk-EHM6AMMA.js";
8
- import "./chunk-NCPWAWIM.js";
9
- import "./chunk-C7A7TKSY.js";
10
- import "./chunk-5N3FYFBV.js";
8
+ import "./chunk-AMHCOB4D.js";
9
+ import "./chunk-E2QN2M7S.js";
10
+ import "./chunk-CWAWATL4.js";
11
11
  import "./chunk-UND73EOB.js";
12
12
  import "./chunk-DGUM43GV.js";
13
13
  export {
@@ -15,4 +15,4 @@ export {
15
15
  resolveSearchSource,
16
16
  resolveTablesSource
17
17
  };
18
- //# sourceMappingURL=source-resolver-PCASPRSD.js.map
18
+ //# sourceMappingURL=source-resolver-4SUWXUGW.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -11,7 +11,7 @@ import {
11
11
  import {
12
12
  createConnector,
13
13
  resolveConnection
14
- } from "./chunk-NCPWAWIM.js";
14
+ } from "./chunk-AMHCOB4D.js";
15
15
  import {
16
16
  detail,
17
17
  error,
@@ -20,8 +20,8 @@ import {
20
20
  success,
21
21
  warning
22
22
  } from "./chunk-HJVVHYVN.js";
23
- import "./chunk-C7A7TKSY.js";
24
- import "./chunk-5N3FYFBV.js";
23
+ import "./chunk-E2QN2M7S.js";
24
+ import "./chunk-CWAWATL4.js";
25
25
  import "./chunk-UND73EOB.js";
26
26
  import "./chunk-DGUM43GV.js";
27
27
 
@@ -365,4 +365,4 @@ async function runSyncWarehouse(projectDir, options) {
365
365
  export {
366
366
  runSyncWarehouse
367
367
  };
368
- //# sourceMappingURL=sync-warehouse-XC7YYZKC.js.map
368
+ //# sourceMappingURL=sync-warehouse-NZFDS6WK.js.map
@@ -1,11 +1,11 @@
1
1
  import {
2
2
  resolveTablesSource
3
- } from "./chunk-YMQ4PWVJ.js";
3
+ } from "./chunk-FZFBBB7K.js";
4
4
  import "./chunk-VJC24RKT.js";
5
5
  import "./chunk-EHM6AMMA.js";
6
- import "./chunk-NCPWAWIM.js";
7
- import "./chunk-C7A7TKSY.js";
8
- import "./chunk-5N3FYFBV.js";
6
+ import "./chunk-AMHCOB4D.js";
7
+ import "./chunk-E2QN2M7S.js";
8
+ import "./chunk-CWAWATL4.js";
9
9
  import "./chunk-UND73EOB.js";
10
10
  import "./chunk-DGUM43GV.js";
11
11
 
@@ -27,4 +27,4 @@ async function listTables(projectDir, options) {
27
27
  export {
28
28
  listTables
29
29
  };
30
- //# sourceMappingURL=tables-26PNVZIC.js.map
30
+ //# sourceMappingURL=tables-WJS2VI4L.js.map
@@ -25,7 +25,7 @@ yamchart advisor # AI-powered dbt model advisor
25
25
  - `yamchart.yaml` — project config, default connection, theme, auth
26
26
  - `connections/*.yaml` — database connections (DuckDB, Postgres, MySQL, SQLite, Snowflake)
27
27
  - `models/*.sql` — SQL with Jinja templating (`{{ param }}`, `{% if %}`, `{{ ref('table') }}`)
28
- - `charts/*.yaml` — chart definitions (line, bar, area, pie, donut, scatter, kpi, combo, heatmap, funnel, waterfall, gauge, table)
28
+ - `charts/*.yaml` — chart definitions (line, bar, area, pie, donut, scatter, kpi, combo, heatmap, funnel, waterfall, gauge, sankey, table)
29
29
  - `dashboards/*.yaml` — 12-column grid layouts with chart/text widgets, optional tabs
30
30
  - `schedules/*.yaml` — cron-based Slack reports and threshold alerts
31
31
 
@@ -259,6 +259,25 @@ chart:
259
259
  format: ".1f"
260
260
  ```
261
261
 
262
+ **Sankey** — flow/navigation visualization using pre-aggregated source→target→value rows:
263
+ ```yaml
264
+ chart:
265
+ type: sankey
266
+ source: { field: from_page } # Source node field
267
+ target: { field: to_page } # Target node field
268
+ value: { field: transitions } # Flow value field
269
+ levels: # Optional — explicit node depth ordering
270
+ - [Homepage, Blog, Resources] # Left column
271
+ - [Product, Solutions] # Middle column
272
+ - [Conversion] # Right column
273
+ orient: horizontal # horizontal (default) or vertical
274
+ node_width: 20 # Node bar width in px (default: 20)
275
+ node_gap: 12 # Gap between nodes in px (default: 12)
276
+ label_position: right # Label position: left, right, top, bottom, inside
277
+ ```
278
+
279
+ Ideal for page navigation flows, conversion funnels, and resource allocation visualization. Nodes auto-arrange by flow when `levels` is omitted. Hover highlights connected paths. Right-click for drill-down/cross-filter.
280
+
262
281
  **Table** — sortable data table with optional sticky columns, summary row, pagination, and overflow control:
263
282
  ```yaml
264
283
  chart:
@@ -275,6 +294,11 @@ chart:
275
294
  enabled: true
276
295
  page_size: 50 # Default: 50, options shown: 25/50/100/All
277
296
  mode: paginated # paginated (default) or infinite_scroll
297
+ server_side: true # Optional — force server-side LIMIT/OFFSET pagination
298
+ max_rows: 10000 # Optional — display cap (default: 10000, auto-activates server-side pagination)
299
+ export: # Optional — CSV export settings (enabled by default)
300
+ enabled: true
301
+ max_rows: 500000 # Export row cap (default: 500000)
278
302
  ```
279
303
 
280
304
  Column headers are automatically humanized (`total_revenue` → "Total Revenue") unless an explicit `label` is set.
@@ -284,6 +308,9 @@ Column headers are automatically humanized (`total_revenue` → "Total Revenue")
284
308
  **Overflow:** Per-column `overflow` controls long content: `ellipsis` (default — truncate with tooltip on hover), `wrap` (word-break), `hidden` (clip without tooltip). Useful for wallet addresses, hashes, or URLs.
285
309
  **Font:** `font: mono` renders a column in monospace — ideal for hashes, IDs, and code values.
286
310
  **Pagination:** When enabled, large tables show page controls (Previous/Next, page size selector with 25/50/100/All). `infinite_scroll` mode loads rows progressively as you scroll. Without pagination, all rows render (fine for small tables, slow for 1000+ rows).
311
+ **Server-side pagination:** Tables exceeding `max_rows` (default 10,000) automatically switch to server-side pagination with `LIMIT/OFFSET` queries, a parallel `COUNT(*)` for total rows, and server-computed summary aggregations. A notice banner shows "Showing first 10,000 of ~208,000 rows — use Export for full data". Set `pagination.server_side: true` to force this mode, or configure `max_rows` per-chart. Project-wide defaults: `defaults.table.max_display_rows` and `defaults.table.max_export_rows` in `yamchart.yaml`.
312
+ **Export:** CSV export is available on all table charts via the actions menu (three-dot icon). Downloads are bounded by `export.max_rows` (default 500K) to prevent runaway exports. The export endpoint streams CSV directly from the warehouse.
313
+ **Copy Table:** The actions menu includes "Copy Table" which copies all visible data as tab-delimited text to the clipboard (pastes into Excel/Sheets). Keyboard shortcuts: Cmd+A to select all, Cmd+C to copy. Click row numbers for row selection, Shift/Cmd+click headers for column selection.
287
314
 
288
315
  **Pivot table** — transform flat query results into grouped, pivoted tables with subtotals and heatmap coloring:
289
316
  ```yaml
@@ -347,6 +374,82 @@ series:
347
374
  gradient: true # or { from: "#hex", to: "#hex" }
348
375
  ```
349
376
 
377
+ **Rolling average** — compute moving average client-side:
378
+ ```yaml
379
+ series:
380
+ columns:
381
+ - field: sessions
382
+ name: Daily Sessions
383
+ color: "#93C5FD"
384
+ opacity: 0.4
385
+ - field: sessions
386
+ name: 7-Day Avg
387
+ color: "#2563EB"
388
+ transform: { type: rolling_average, window: 7 }
389
+ ```
390
+
391
+ **Range bands** — shaded area between two series (forecast confidence intervals):
392
+ ```yaml
393
+ series:
394
+ columns:
395
+ - field: actual
396
+ name: Actual
397
+ color: "#2563EB"
398
+ - field: forecast_low
399
+ name: Forecast Range
400
+ color: "#93C5FD"
401
+ band: Forecast Upper # Pairs with another column by name
402
+ - field: forecast_high
403
+ name: Forecast Upper
404
+ color: "#93C5FD"
405
+ ```
406
+
407
+ ### Annotations
408
+
409
+ Add reference lines and markers to line, bar, area, and combo charts:
410
+ ```yaml
411
+ chart:
412
+ type: line
413
+ annotations:
414
+ - type: line # Horizontal y-axis reference line
415
+ value: 1000
416
+ label: Target
417
+ color: "#22C55E"
418
+ style: dashed # solid, dashed, dotted
419
+ - type: band # Shaded y-axis range
420
+ from: 800
421
+ to: 1200
422
+ label: Target Zone
423
+ color: "rgba(34, 197, 94, 0.1)"
424
+ - type: x_line # Vertical x-axis marker (date or category)
425
+ x: "2026-03-13"
426
+ label: Launch
427
+ color: "#EF4444"
428
+ style: dashed
429
+ ```
430
+
431
+ ### Period-over-Period Comparisons
432
+
433
+ Overlay prior periods on the same chart to compare trends (WoW, MoM, YoY):
434
+ ```yaml
435
+ chart:
436
+ type: line
437
+ x: { field: date, type: temporal }
438
+ y: { field: sessions }
439
+ compare:
440
+ - period: previous_week # Shifts dates back by 7 days
441
+ label: Last Week
442
+ style: dashed
443
+ opacity: 0.5
444
+ color: "#94A3B8"
445
+ - period: previous_year # Shifts dates back by 12 months
446
+ label: Last Year
447
+ style: dotted
448
+ opacity: 0.3
449
+ ```
450
+
451
+ The server automatically re-queries the model with shifted date ranges and returns comparison data aligned by position (not date). Supported periods: `previous_week`, `previous_month`, `previous_quarter`, `previous_year`. Works with any date preset or custom range.
452
+
350
453
  ### Stacking
351
454
 
352
455
  ```yaml
@@ -400,7 +503,7 @@ parameters:
400
503
 
401
504
  ### Drill-Down
402
505
 
403
- Click any chart element to open a context menu with options to cross-filter, view detail data, or navigate to a related chart.
506
+ Right-click any chart element to open a context menu with options to cross-filter, view detail data, or navigate to a related chart.
404
507
 
405
508
  ```yaml
406
509
  drillDown:
@@ -536,7 +639,7 @@ Press `⌘K` (Mac) or `Ctrl+K` (Windows/Linux) to open the search modal from any
536
639
 
537
640
  ### Cross-Filtering
538
641
 
539
- Click any chart element on a dashboard to filter all other charts by the clicked dimension. Cross-filters are dashboard-scoped and auto-clear on navigation.
642
+ Right-click any chart element on a dashboard to filter all other charts by the clicked dimension. Cross-filters are dashboard-scoped and auto-clear on navigation.
540
643
 
541
644
  ### Edit Mode
542
645
 
@@ -697,6 +800,9 @@ defaults:
697
800
  cache_ttl: "30m" # Default cache TTL
698
801
  base_url: https://bi.example.com # For SSO callbacks and alert links
699
802
  week_start: monday # sunday–saturday
803
+ table: # Table chart defaults
804
+ max_display_rows: 10000 # Auto-switch to server-side pagination above this (default: 10000)
805
+ max_export_rows: 500000 # CSV export row cap (default: 500000)
700
806
 
701
807
  theme:
702
808
  base: modern # Preset: modern (default), executive, minimal
@@ -1053,6 +1159,62 @@ chart:
1053
1159
 
1054
1160
  All goal types (fixed, cumulative, model) support a `zone` with `tolerance` (0–1). This renders a semi-transparent shaded band around the goal line at ±tolerance of the target value. For example, `tolerance: 0.10` on a target of 100,000 shades the 90,000–110,000 range.
1055
1161
 
1162
+ ## AI Chat
1163
+
1164
+ AI chat adds an interactive assistant to the web UI. Enable it in `yamchart.yaml`:
1165
+
1166
+ ```yaml
1167
+ features:
1168
+ chat: true
1169
+ ```
1170
+
1171
+ Requires `ANTHROPIC_API_KEY` environment variable. When disabled or key is missing, all chat UI elements and endpoints are hidden.
1172
+
1173
+ ### Built-in Agents
1174
+
1175
+ | Agent | Description |
1176
+ |-------|-------------|
1177
+ | Dashboard Analyst | Summarizes dashboards, explains trends, answers data questions, applies filters |
1178
+ | SQL Explorer | Runs ad-hoc SQL queries, compares periods, deep-dives into raw data |
1179
+
1180
+ ### Custom Agents
1181
+
1182
+ Define project-specific agents in `agents/*.yaml`:
1183
+
1184
+ ```yaml
1185
+ name: finance-analyst
1186
+ title: Finance Analyst
1187
+ description: Expert in revenue metrics, P&L, and financial KPIs
1188
+ system_prompt: |
1189
+ You are a finance analyst for {{project.name}}.
1190
+ Focus on revenue, margins, and cost metrics.
1191
+ tools:
1192
+ - query_model
1193
+ - compare_periods
1194
+ - get_dashboard_summary
1195
+ model: claude-sonnet-4-5-20250929 # optional model override
1196
+ models: # optional - restrict accessible models
1197
+ - revenue_*
1198
+ - costs_*
1199
+ ```
1200
+
1201
+ ### Available Tools
1202
+
1203
+ | Tool | Description |
1204
+ |------|-------------|
1205
+ | `query_model` | Execute a SQL model with parameters |
1206
+ | `get_chart_config` | Read a chart's full YAML config |
1207
+ | `get_dashboard_summary` | List charts, types, and cached KPI values |
1208
+ | `apply_filter` | Set a dashboard filter (updates UI in real-time) |
1209
+ | `run_sql` | Execute ad-hoc SQL against the connected database |
1210
+ | `compare_periods` | Run a model with two date ranges and diff results |
1211
+
1212
+ ### Entry Points
1213
+
1214
+ - **Ask AI button** on chart headers and dashboard toolbar
1215
+ - **Cmd+K search** — "Ask AI: {query}" option when no exact match
1216
+ - **Sidebar chat tab** — general conversation without specific context
1217
+
1056
1218
  ## Number Formats (d3-format)
1057
1219
 
1058
1220
  | Format | Output | Use |
@@ -28,3 +28,6 @@ defaults:
28
28
  # enabled: true
29
29
  # defaults:
30
30
  # base_url: https://bi.example.com
31
+
32
+ # features:
33
+ # chat: true # Enable AI chat (requires ANTHROPIC_API_KEY)
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  createConnector,
3
3
  resolveConnection
4
- } from "./chunk-NCPWAWIM.js";
4
+ } from "./chunk-AMHCOB4D.js";
5
5
  import {
6
6
  detail,
7
7
  error,
@@ -10,15 +10,16 @@ import {
10
10
  success,
11
11
  warning
12
12
  } from "./chunk-HJVVHYVN.js";
13
- import "./chunk-C7A7TKSY.js";
13
+ import "./chunk-E2QN2M7S.js";
14
14
  import {
15
15
  createTemplateContext,
16
16
  expandDatePreset,
17
17
  isDatePreset,
18
18
  parseModelMetadata,
19
19
  renderTemplate,
20
+ resolveDynamicDefault,
20
21
  runAll
21
- } from "./chunk-5N3FYFBV.js";
22
+ } from "./chunk-CWAWATL4.js";
22
23
  import "./chunk-UND73EOB.js";
23
24
  import "./chunk-DGUM43GV.js";
24
25
 
@@ -58,19 +59,6 @@ async function testProject(projectDir, modelFilter, options) {
58
59
  await connector.disconnect();
59
60
  }
60
61
  }
61
- function resolveDynamicDefault(value) {
62
- const trimmed = value.trim().toLowerCase();
63
- if (trimmed === "current_date()" || trimmed === "current_date" || trimmed === "now()" || trimmed.includes("current_date")) {
64
- return formatISODate(/* @__PURE__ */ new Date());
65
- }
66
- return value;
67
- }
68
- function formatISODate(date) {
69
- const y = date.getFullYear();
70
- const m = String(date.getMonth() + 1).padStart(2, "0");
71
- const d = String(date.getDate()).padStart(2, "0");
72
- return `${y}-${m}-${d}`;
73
- }
74
62
  function compileWithDefaults(sql, metadata, refs) {
75
63
  const params = {};
76
64
  if (metadata.params) {
@@ -192,4 +180,4 @@ export {
192
180
  formatTestOutput,
193
181
  testProject
194
182
  };
195
- //# sourceMappingURL=test-APA44AIF.js.map
183
+ //# sourceMappingURL=test-I4XOF7TZ.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/commands/test.ts"],"sourcesContent":["import { readFile, readdir } from 'fs/promises';\nimport { join, extname } from 'path';\nimport {\n parseModelMetadata,\n runAll,\n renderTemplate,\n createTemplateContext,\n expandDatePreset,\n isDatePreset,\n resolveDynamicDefault,\n type TestSuiteResult,\n type TestModelInput,\n} from '@yamchart/query';\nimport type { ModelMetadata } from '@yamchart/schema';\nimport * as output from '../utils/output.js';\nimport { resolveConnection, createConnector } from './connection-utils.js';\n\nexport interface TestOptions {\n connection?: string;\n json?: boolean;\n}\n\nexport interface TestResult {\n success: boolean;\n suite: TestSuiteResult;\n connectionName: string;\n}\n\nexport async function testProject(\n projectDir: string,\n modelFilter: string | undefined,\n options: TestOptions,\n): Promise<TestResult> {\n const connection = await resolveConnection(projectDir, options.connection);\n const connector = createConnector(connection, projectDir);\n\n const modelsDir = join(projectDir, 'models');\n const allModels = await loadModels(modelsDir);\n\n let modelsToTest = allModels;\n if (modelFilter) {\n modelsToTest = allModels.filter((m) => m.name === modelFilter);\n if (modelsToTest.length === 0) {\n throw new Error(`Model \"${modelFilter}\" not found`);\n }\n }\n\n // Build refs map: model name -> model name (identity for standalone execution)\n const refs: Record<string, string> = {};\n for (const m of allModels) {\n refs[m.name] = m.name;\n }\n\n const testInputs: TestModelInput[] = [];\n for (const model of modelsToTest) {\n try {\n const compiledSql = compileWithDefaults(model.sql, model.metadata, refs);\n testInputs.push({ compiledSql, metadata: model.metadata });\n } catch {\n // If compilation fails, include raw SQL so the error is reported during test execution\n testInputs.push({ compiledSql: model.sql, metadata: model.metadata });\n }\n }\n\n try {\n await connector.connect();\n const suite = await runAll(testInputs, connector);\n return { success: suite.failed === 0, suite, connectionName: connection.name };\n } finally {\n await connector.disconnect();\n }\n}\n\nfunction compileWithDefaults(\n sql: string,\n metadata: ModelMetadata,\n refs: Record<string, string>,\n): string {\n const params: Record<string, unknown> = {};\n if (metadata.params) {\n for (const p of metadata.params) {\n if (p.default !== undefined) {\n params[p.name] = resolveDynamicDefault(p.default);\n }\n }\n }\n\n // Expand date presets into start_date/end_date\n if (typeof params.date_range === 'string' && isDatePreset(params.date_range)) {\n const range = expandDatePreset(params.date_range);\n if (range) {\n params.start_date = range.start_date;\n params.end_date = range.end_date;\n }\n }\n\n const context = createTemplateContext(params, refs);\n return renderTemplate(sql, context);\n}\n\ninterface LoadedModel {\n name: string;\n sql: string;\n metadata: ModelMetadata;\n}\n\nasync function loadModels(dir: string): Promise<LoadedModel[]> {\n const models: LoadedModel[] = [];\n\n let entries;\n try {\n entries = await readdir(dir, { withFileTypes: true });\n } catch {\n return models;\n }\n\n for (const entry of entries) {\n const fullPath = join(dir, entry.name);\n if (entry.isDirectory()) {\n const subModels = await loadModels(fullPath);\n models.push(...subModels);\n } else if (extname(entry.name) === '.sql') {\n const content = await readFile(fullPath, 'utf-8');\n try {\n const parsed = parseModelMetadata(content);\n models.push({ name: parsed.name, sql: parsed.sql, metadata: parsed });\n } catch {\n // Skip unparseable models\n }\n }\n }\n\n return models;\n}\n\nexport function formatTestOutput(result: TestResult, connectionName: string): void {\n const { suite } = result;\n\n const testedModels = suite.models.filter(\n (m) => m.schemaCheck || m.assertions.length > 0 || m.error,\n );\n\n output.header(`Testing ${suite.models.length} model(s) against ${connectionName}...`);\n\n for (const model of suite.models) {\n const hasChecks = model.schemaCheck || model.assertions.length > 0 || model.error;\n if (!hasChecks) continue;\n\n console.log(` ${model.modelName}`);\n\n if (model.error) {\n output.error(` error: ${model.error}`);\n continue;\n }\n\n if (model.schemaCheck) {\n formatSchemaCheck(model.schemaCheck);\n }\n\n for (const assertion of model.assertions) {\n formatAssertion(assertion);\n }\n\n output.newline();\n }\n\n // Summary line\n const totalChecks = suite.passed + suite.failed;\n const summary = `${testedModels.length} model(s), ${totalChecks} check(s): ${suite.passed} passed, ${suite.failed} failed`;\n const skippedSuffix = suite.skipped > 0 ? ` (${suite.skipped} skipped)` : '';\n const durationSuffix = ` (${suite.durationMs.toFixed(0)}ms)`;\n\n if (suite.failed === 0) {\n output.success(`${summary}${skippedSuffix}${durationSuffix}`);\n } else {\n output.error(`${summary}${skippedSuffix}${durationSuffix}`);\n }\n}\n\nfunction formatSchemaCheck(check: NonNullable<TestSuiteResult['models'][0]['schemaCheck']>): void {\n if (check.passed) {\n output.success(` schema: returns expected columns (${check.expectedColumns.join(', ')})`);\n return;\n }\n\n const issues: string[] = [];\n if (check.missingColumns.length > 0) {\n issues.push(`missing: ${check.missingColumns.join(', ')}`);\n }\n for (const m of check.typeMismatches) {\n issues.push(`${m.column}: expected ${m.expected}, got ${m.actual}`);\n }\n output.error(` schema: ${issues.join('; ')}`);\n}\n\nfunction formatAssertion(assertion: TestSuiteResult['models'][0]['assertions'][0]): void {\n const label = extractAssertionLabel(assertion.sql);\n\n if (assertion.warning) {\n output.warning(` ${label}`);\n output.detail(` ${assertion.warning}`);\n }\n\n if (assertion.error) {\n output.error(` test: ${label} -- error`);\n output.detail(` ${assertion.error}`);\n } else if (assertion.passed) {\n output.success(` test: ${label}`);\n } else {\n output.error(` test: ${label} -- ${assertion.violationCount} failing row(s)`);\n if (assertion.sampleViolations) {\n for (const row of assertion.sampleViolations.slice(0, 3)) {\n output.detail(` ${JSON.stringify(row)}`);\n }\n }\n }\n}\n\nfunction extractAssertionLabel(sql: string): string {\n const whereMatch = sql.match(/WHERE\\s+(.+?)$/i);\n if (whereMatch?.[1]) {\n const clause = whereMatch[1].trim();\n return clause.length > 60 ? clause.slice(0, 57) + '...' : clause;\n }\n return sql.length > 60 ? sql.slice(0, 57) + '...' : sql;\n}\n"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "yamchart",
3
- "version": "0.8.7",
3
+ "version": "0.9.0",
4
4
  "description": "Git-native business intelligence dashboards",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -66,8 +66,9 @@
66
66
  "typescript": "^5.7.0",
67
67
  "vitest": "^2.1.0",
68
68
  "@yamchart/advisor": "0.1.0",
69
- "@yamchart/config": "0.1.2",
70
69
  "@yamchart/auth-local": "0.1.0",
70
+ "@yamchart/chat": "0.1.0",
71
+ "@yamchart/config": "0.1.2",
71
72
  "@yamchart/query": "0.1.2",
72
73
  "@yamchart/schema": "0.1.2",
73
74
  "@yamchart/server": "0.1.2"