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}