bnto-core 0.1.3

Core WASM engine library for Bnto — shared types, traits, and orchestration
Documentation
// =============================================================================
// bnto-core — The Foundation WASM Library
// =============================================================================
//
// Shared foundation for all Bnto WASM node crates: error types,
// the NodeProcessor trait, progress reporting, pipeline execution,
// and the node registry. This is an rlib -- it doesn't produce a
// .wasm file itself. That's the job of the bnto-wasm entry point.

// --- Public Modules ---
// These are the building blocks that node crates and the web app will use.

/// Centralized string case/formatting — lower, upper, title, slug, deslug, etc.
/// The Rust equivalent of JS's `change-case` library.
pub mod case;

/// Controlled system access for processors that need external tools.
/// Browser gets `NoopContext`, CLI gets `NativeContext`, desktop gets `SandboxedContext`.
pub mod context;

/// Error types for the WASM engine.
/// Every error that can happen during node execution is defined here.
pub mod errors;

/// Structured pipeline events — rich progress reporting for multi-node execution.
/// Powers per-node status highlighting in the editor, progress bars, and error display.
pub mod events;

/// Definition JSON Schema — validates `.bnto.json` files.
/// Generates a JSON Schema (Draft 2020-12) describing the Definition structure
/// so any consumer can validate recipe files without reimplementing TS types.
pub mod definition_schema;

/// Document-shape types for `.bnto.json` files — the authoring view.
/// Mirrors the TypeScript `Definition` types and round-trips every real
/// recipe fixture. Distinct from `pipeline` (the execution-pruned view).
pub mod definition;

/// Node metadata types — self-describing processor definitions.
/// Each processor declares its name, category, parameters, accepted MIME types,
/// and whether it runs in the browser. Powers the `node_catalog()` WASM export.
pub mod metadata;

/// The pipeline executor — walks nodes, iterates files, chains outputs.
/// This is the engine's brain. See `.claude/strategy/engine-execution.md`.
pub mod executor;

/// Pipeline definition types — what the engine receives to execute.
/// Mirrors the TypeScript `PipelineDefinition` / `PipelineNode` types.
pub mod pipeline;

/// The NodeProcessor trait — the contract every node type must implement.
/// If you're building a new node (like image compression), you implement this.
pub mod processor;

/// Progress reporting — how nodes tell the UI "I'm 50% done".
/// Uses target-agnostic closures (no WASM dependency).
pub mod progress;

/// Node registry — maps node type keys (e.g., "image-compress") to processors.
/// Replaces the JS-side `wasmLoader.ts` registry.
pub mod registry;

/// Node-level field declarations — typed user-facing controls for nodes.
/// Field values are substituted into `{{fields.*}}` templates at execution time.
pub mod field_def;

/// Editor state model — pure-data recipe editing state.
/// Shared by all editor surfaces (TUI List, Wizard, Code, Graph).
/// No TUI dependency. Sprint 15 extracts to standalone `bnto-editor` crate.
pub mod editor;

/// Engine-level logging — trait + types for structured diagnostics.
/// Browser gets `NoopLogger`, CLI gets `FileLogger`.
pub mod logging;

/// File content abstraction — in-memory bytes or on-disk path reference.
/// Large files (shell-command outputs) stay on disk as path references to
/// avoid reading multi-GB files into memory.
pub mod file_data;

/// Simple dotenv parser — loads KEY=VALUE pairs from `.env` files.
/// No crate dependency, handles comments, quotes, and `export` prefix.
pub mod dotenv;

/// Secret declarations for recipes — pre-flight validation of required env vars.
/// Recipes declare what secrets they need; the engine validates before execution.
pub mod secrets;

/// Version constraint checking for external dependencies.
/// Parses `--version` output, extracts version numbers, validates against
/// constraint strings like `">=6.0"`.
pub mod version;

// --- Re-exports ---
// These `pub use` statements let users import directly from the crate root.
// Instead of writing `use bnto_core::errors::BntoError`, they can write
// `use bnto_core::BntoError`. Convenience!
pub use context::{NoopContext, ProcessContext};
pub use definition::{Definition, Edge, Metadata, Port, Position};
pub use definition_schema::definition_json_schema;
pub use editor::{EditorError, EditorModel, EditorNode, EditorSnapshot, EditorSource};
pub use errors::BntoError;
pub use events::{PipelineEvent, PipelineReporter};
pub use executor::execute_pipeline;
pub use executor::template::{
    build_input_metadata, build_node_outputs_for_input, resolve_ctx_templates,
    resolve_node_templates,
};
pub use field_def::{FieldDef, FieldDefs, FieldOption};
pub use file_data::FileData;
pub use logging::{LogEntry, LogLevel, Logger, NoopLogger};
pub use metadata::{
    Constraints, Dependency, InputCardinality, NodeCategory, NodeMetadata, NodeTypeInfo,
    ParamCondition, ParamConditionEntry, ParameterDef, ParameterType, all_node_types,
    io_container::io_container_param_defs, node_type_params,
};
pub use pipeline::{
    InputMode, IterationMode, PipelineDefinition, PipelineFile, PipelineFileResult, PipelineNode,
    PipelineResult, PipelineSettings, first_processing_node_id, resolve_input_mode,
    resolve_output_directory, resolve_output_mode,
};
pub use processor::{BatchFile, BatchInput, NodeProcessor};
pub use progress::ProgressReporter;
pub use registry::NodeRegistry;
pub use secrets::SecretDef;
pub use version::{VersionCheckResult, VersionConstraint, check_version};

// =============================================================================
// Shared Constants
// =============================================================================
//
// Constants used by multiple node crates live here so there's a single source
// of truth. When compress, resize, and convert all need the same default JPEG
// quality, defining it once in bnto-core prevents the values from drifting
// apart over time.

/// The current `.bnto.json` format version.
///
/// This must stay in sync with `CURRENT_FORMAT_VERSION` in `@bnto/nodes`.
/// The WASM engine uses this to verify that a definition it receives is
/// compatible with the node processors it has compiled in.
///
/// Semver rules: definitions with the same major version are compatible.
/// A definition at "1.3.0" works fine on an engine that supports "1.0.0".
pub const FORMAT_VERSION: &str = "1.0.0";

/// Default quality when not specified by the user (1-100 scale).
/// 80 is the industry sweet spot: significant file size savings with barely
/// noticeable quality loss for most photos. Used by all image operations
/// (compress, resize, convert).
pub const DEFAULT_QUALITY: u8 = 80;

// =============================================================================
// Utility Functions (Pure Rust — no WASM boundary)
// =============================================================================
//
// NOTE: setup(), version(), and greet() used to live here with #[wasm_bindgen]
// attributes. They've moved to the bnto-wasm entry point crate which is the
// single cdylib that produces the .wasm file for the browser. This crate is
// now purely an rlib (Rust library) — no JS exports.
//
// These utility functions remain available as regular Rust functions for use
// by other crates in the workspace and for testing.

/// Returns the version of the bnto-core crate.
pub fn version() -> String {
    env!("CARGO_PKG_VERSION").to_string()
}

// =============================================================================
// Tests
// =============================================================================

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_format_version_is_valid_semver() {
        // FORMAT_VERSION should be a valid semver string (major.minor.patch)
        let parts: Vec<&str> = FORMAT_VERSION.split('.').collect();
        assert_eq!(
            parts.len(),
            3,
            "FORMAT_VERSION must have 3 parts (major.minor.patch)"
        );
        for part in &parts {
            part.parse::<u32>()
                .expect("Each semver part must be a valid number");
        }
    }

    #[test]
    fn test_version_returns_cargo_version() {
        let v = version();
        assert!(!v.is_empty(), "Version string should not be empty");
        assert!(
            v.contains('.'),
            "Version should contain dots (semver format)"
        );
    }
}