Skip to main content

chartml_core/pipeline/
mod.rs

1//! Three-stage rendering pipeline types (chartml 5.0 phase 2).
2//!
3//! The pipeline splits chart rendering into three explicit stages so each
4//! has a single typed responsibility:
5//!
6//! ```text
7//!   YAML ──► FETCH ──► FetchedChart ──► TRANSFORM ──► PreparedChart ──► render ──► SVG
8//!            (async)                    (async)                          (sync)
9//! ```
10//!
11//! Phase 2 introduces the type shapes and exposes the explicit
12//! `ChartML::fetch` / `transform` / `render_prepared_to_svg` /
13//! `render_to_svg_async` methods. No provider trait yet — `fetch` reads
14//! from pre-registered sources only. Phase 3 adds the provider trait,
15//! resolver, cache, and parallel fetch.
16//!
17//! All types derive `Clone` because:
18//! - `DataTable` is `Arc`-backed internally, so cloning sources is cheap;
19//! - `Clone` enables the "fetch + transform once, resize-render N times"
20//!   use case (e.g. responsive layouts) without re-running upstream stages.
21
22use std::collections::HashMap;
23// `web_time::SystemTime` is a wasm32-compatible drop-in for `std::time::SystemTime`.
24use web_time::SystemTime;
25
26use arrow::array::RecordBatch;
27use arrow::datatypes::SchemaRef;
28use indexmap::IndexMap;
29
30use crate::data::DataTable;
31use crate::spec::ChartSpec;
32
33mod render_options;
34
35pub use render_options::RenderOptions;
36
37/// Stage 1 output: the spec plus every named source resolved to a `DataTable`.
38///
39/// `sources` always has at least one entry. The key is the user-chosen name
40/// from the YAML `data:` map; for unnamed flat data the canonical key is
41/// `"source"`. Insertion order matches the YAML — preserved via `IndexMap`
42/// so transform middleware can rely on stable ordering.
43#[derive(Debug, Clone)]
44pub struct FetchedChart {
45    pub spec: ChartSpec,
46    pub sources: IndexMap<String, DataTable>,
47    /// Original provider batches per source. Populated on cache miss (from
48    /// `fetch_batches`) or from pre-registered DataTables. When present,
49    /// `transform_batches()` is called instead of `transform()`.
50    pub batch_sources: Option<IndexMap<String, (SchemaRef, Vec<RecordBatch>)>>,
51    pub metadata: FetchMetadata,
52}
53
54/// Stage 2 output: spec plus the single transformed table the renderer will consume.
55///
56/// `data` is either the lone source (passthrough when no transform is declared)
57/// or the result of running registered `TransformMiddleware` over the
58/// fetched sources.
59#[derive(Debug, Clone)]
60pub struct PreparedChart {
61    pub spec: ChartSpec,
62    pub data: DataTable,
63    pub metadata: PreparedMetadata,
64}
65
66/// Per-fetch telemetry. `per_source` stays empty in phase 2 (no provider
67/// dispatch yet); phase 3 populates it from `FetchResult.metadata` per
68/// declared source.
69#[derive(Debug, Clone)]
70pub struct FetchMetadata {
71    pub refreshed_at: SystemTime,
72    /// Source names served from cache. Empty in phase 2 (no resolver yet).
73    pub cache_hits: Vec<String>,
74    /// Source names that produced a fresh fetch. Empty in phase 2 (no resolver yet).
75    pub cache_misses: Vec<String>,
76    /// Per-source provider metadata (e.g. `bytes_billed`, `rows_returned`).
77    /// Empty in phase 2 — populated once providers exist.
78    pub per_source: HashMap<String, HashMap<String, serde_json::Value>>,
79}
80
81impl FetchMetadata {
82    /// Construct empty metadata stamped at the current wall clock. Used by
83    /// phase 2's `fetch` (which only reads from pre-registered sources).
84    pub fn empty_now() -> Self {
85        Self {
86            refreshed_at: SystemTime::now(),
87            cache_hits: Vec::new(),
88            cache_misses: Vec::new(),
89            per_source: HashMap::new(),
90        }
91    }
92}
93
94/// Per-transform telemetry. Captures whether the middleware actually ran
95/// (vs. passthrough) plus the names of the sources the transform consumed.
96#[derive(Debug, Clone)]
97pub struct PreparedMetadata {
98    pub refreshed_at: SystemTime,
99    /// `true` when transform middleware was invoked; `false` for the
100    /// single-source passthrough path.
101    pub transform_applied: bool,
102    /// Names of every source that fed the transform stage. Insertion order
103    /// matches the YAML — preserved by carrying the `IndexMap` keys
104    /// directly out of stage 1.
105    pub sources_used: Vec<String>,
106}