bnto_core/lib.rs
1// =============================================================================
2// bnto-core — The Foundation WASM Library
3// =============================================================================
4//
5// Shared foundation for all Bnto WASM node crates: error types,
6// the NodeProcessor trait, progress reporting, pipeline execution,
7// and the node registry. This is an rlib -- it doesn't produce a
8// .wasm file itself. That's the job of the bnto-wasm entry point.
9
10// --- Public Modules ---
11// These are the building blocks that node crates and the web app will use.
12
13/// Centralized string case/formatting — lower, upper, title, slug, deslug, etc.
14/// The Rust equivalent of JS's `change-case` library.
15pub mod case;
16
17/// Controlled system access for processors that need external tools.
18/// Browser gets `NoopContext`, CLI gets `NativeContext`, desktop gets `SandboxedContext`.
19pub mod context;
20
21/// Error types for the WASM engine.
22/// Every error that can happen during node execution is defined here.
23pub mod errors;
24
25/// Structured pipeline events — rich progress reporting for multi-node execution.
26/// Powers per-node status highlighting in the editor, progress bars, and error display.
27pub mod events;
28
29/// Definition JSON Schema — validates `.bnto.json` files.
30/// Generates a JSON Schema (Draft 2020-12) describing the Definition structure
31/// so any consumer can validate recipe files without reimplementing TS types.
32pub mod definition_schema;
33
34/// Document-shape types for `.bnto.json` files — the authoring view.
35/// Mirrors the TypeScript `Definition` types and round-trips every real
36/// recipe fixture. Distinct from `pipeline` (the execution-pruned view).
37pub mod definition;
38
39/// Node metadata types — self-describing processor definitions.
40/// Each processor declares its name, category, parameters, accepted MIME types,
41/// and whether it runs in the browser. Powers the `node_catalog()` WASM export.
42pub mod metadata;
43
44/// The pipeline executor — walks nodes, iterates files, chains outputs.
45/// This is the engine's brain. See `.claude/strategy/engine-execution.md`.
46pub mod executor;
47
48/// Pipeline definition types — what the engine receives to execute.
49/// Mirrors the TypeScript `PipelineDefinition` / `PipelineNode` types.
50pub mod pipeline;
51
52/// The NodeProcessor trait — the contract every node type must implement.
53/// If you're building a new node (like image compression), you implement this.
54pub mod processor;
55
56/// Progress reporting — how nodes tell the UI "I'm 50% done".
57/// Uses target-agnostic closures (no WASM dependency).
58pub mod progress;
59
60/// Node registry — maps node type keys (e.g., "image-compress") to processors.
61/// Replaces the JS-side `wasmLoader.ts` registry.
62pub mod registry;
63
64/// Node-level field declarations — typed user-facing controls for nodes.
65/// Field values are substituted into `{{fields.*}}` templates at execution time.
66pub mod field_def;
67
68/// Editor state model — pure-data recipe editing state.
69/// Shared by all editor surfaces (TUI List, Wizard, Code, Graph).
70/// No TUI dependency. Sprint 15 extracts to standalone `bnto-editor` crate.
71pub mod editor;
72
73/// Engine-level logging — trait + types for structured diagnostics.
74/// Browser gets `NoopLogger`, CLI gets `FileLogger`.
75pub mod logging;
76
77/// File content abstraction — in-memory bytes or on-disk path reference.
78/// Large files (shell-command outputs) stay on disk as path references to
79/// avoid reading multi-GB files into memory.
80pub mod file_data;
81
82/// Simple dotenv parser — loads KEY=VALUE pairs from `.env` files.
83/// No crate dependency, handles comments, quotes, and `export` prefix.
84pub mod dotenv;
85
86/// Secret declarations for recipes — pre-flight validation of required env vars.
87/// Recipes declare what secrets they need; the engine validates before execution.
88pub mod secrets;
89
90/// Version constraint checking for external dependencies.
91/// Parses `--version` output, extracts version numbers, validates against
92/// constraint strings like `">=6.0"`.
93pub mod version;
94
95// --- Re-exports ---
96// These `pub use` statements let users import directly from the crate root.
97// Instead of writing `use bnto_core::errors::BntoError`, they can write
98// `use bnto_core::BntoError`. Convenience!
99pub use context::{NoopContext, ProcessContext};
100pub use definition::{Definition, Edge, Metadata, Port, Position};
101pub use definition_schema::definition_json_schema;
102pub use editor::{EditorError, EditorModel, EditorNode, EditorSnapshot, EditorSource};
103pub use errors::BntoError;
104pub use events::{PipelineEvent, PipelineReporter};
105pub use executor::execute_pipeline;
106pub use executor::template::{
107 build_input_metadata, build_node_outputs_for_input, resolve_ctx_templates,
108 resolve_node_templates,
109};
110pub use field_def::{FieldDef, FieldDefs, FieldOption};
111pub use file_data::FileData;
112pub use logging::{LogEntry, LogLevel, Logger, NoopLogger};
113pub use metadata::{
114 Constraints, Dependency, InputCardinality, NodeCategory, NodeMetadata, NodeTypeInfo,
115 ParamCondition, ParamConditionEntry, ParameterDef, ParameterType, all_node_types,
116 io_container::io_container_param_defs, node_type_params,
117};
118pub use pipeline::{
119 InputMode, IterationMode, PipelineDefinition, PipelineFile, PipelineFileResult, PipelineNode,
120 PipelineResult, PipelineSettings, first_processing_node_id, resolve_input_mode,
121 resolve_output_directory, resolve_output_mode,
122};
123pub use processor::{BatchFile, BatchInput, NodeProcessor};
124pub use progress::ProgressReporter;
125pub use registry::NodeRegistry;
126pub use secrets::SecretDef;
127pub use version::{VersionCheckResult, VersionConstraint, check_version};
128
129// =============================================================================
130// Shared Constants
131// =============================================================================
132//
133// Constants used by multiple node crates live here so there's a single source
134// of truth. When compress, resize, and convert all need the same default JPEG
135// quality, defining it once in bnto-core prevents the values from drifting
136// apart over time.
137
138/// The current `.bnto.json` format version.
139///
140/// This must stay in sync with `CURRENT_FORMAT_VERSION` in `@bnto/nodes`.
141/// The WASM engine uses this to verify that a definition it receives is
142/// compatible with the node processors it has compiled in.
143///
144/// Semver rules: definitions with the same major version are compatible.
145/// A definition at "1.3.0" works fine on an engine that supports "1.0.0".
146pub const FORMAT_VERSION: &str = "1.0.0";
147
148/// Default quality when not specified by the user (1-100 scale).
149/// 80 is the industry sweet spot: significant file size savings with barely
150/// noticeable quality loss for most photos. Used by all image operations
151/// (compress, resize, convert).
152pub const DEFAULT_QUALITY: u8 = 80;
153
154// =============================================================================
155// Utility Functions (Pure Rust — no WASM boundary)
156// =============================================================================
157//
158// NOTE: setup(), version(), and greet() used to live here with #[wasm_bindgen]
159// attributes. They've moved to the bnto-wasm entry point crate which is the
160// single cdylib that produces the .wasm file for the browser. This crate is
161// now purely an rlib (Rust library) — no JS exports.
162//
163// These utility functions remain available as regular Rust functions for use
164// by other crates in the workspace and for testing.
165
166/// Returns the version of the bnto-core crate.
167pub fn version() -> String {
168 env!("CARGO_PKG_VERSION").to_string()
169}
170
171// =============================================================================
172// Tests
173// =============================================================================
174
175#[cfg(test)]
176mod tests {
177 use super::*;
178
179 #[test]
180 fn test_format_version_is_valid_semver() {
181 // FORMAT_VERSION should be a valid semver string (major.minor.patch)
182 let parts: Vec<&str> = FORMAT_VERSION.split('.').collect();
183 assert_eq!(
184 parts.len(),
185 3,
186 "FORMAT_VERSION must have 3 parts (major.minor.patch)"
187 );
188 for part in &parts {
189 part.parse::<u32>()
190 .expect("Each semver part must be a valid number");
191 }
192 }
193
194 #[test]
195 fn test_version_returns_cargo_version() {
196 let v = version();
197 assert!(!v.is_empty(), "Version string should not be empty");
198 assert!(
199 v.contains('.'),
200 "Version should contain dots (semver format)"
201 );
202 }
203}