Skip to main content

briefcase_wasm/
lib.rs

1//! # Briefcase AI WebAssembly Bindings
2//!
3//! High-performance AI observability and decision tracking for JavaScript and TypeScript applications
4//! running in browsers or Node.js environments.
5//!
6//! ## Features
7//!
8//! - **AI Decision Tracking**: Capture inputs, outputs, and context for every AI decision
9//! - **High Performance**: Rust-powered WebAssembly for maximum speed
10//! - **Browser Compatible**: Works in all modern browsers and Node.js
11//! - **Sync APIs**: Simplified synchronous APIs optimized for WebAssembly
12//! - **TypeScript Support**: Full type definitions included
13//! - **Zero Dependencies**: No external JavaScript dependencies required
14//!
15//! ## Installation
16//!
17//! ```bash
18//! npm install briefcase-wasm
19//! ```
20//!
21//! ## Usage
22//!
23//! ```javascript
24//! import { init, DecisionSnapshot, Input, Output } from 'briefcase-wasm';
25//!
26//! // Initialize the WASM module
27//! await init();
28//!
29//! // Create a decision snapshot
30//! const decision = new DecisionSnapshot("ai_function");
31//! const input = new Input("query", "Hello world", "string");
32//! const output = new Output("response", "Hello back!", "string");
33//!
34//! decision.addInput(input);
35//! decision.addOutput(output);
36//! ```
37//!
38//! ## Browser Usage
39//!
40//! For browser environments, you may need to configure your bundler to handle WASM files.
41//! Most modern bundlers like Vite, Webpack 5, and Parcel support WebAssembly out of the box.
42//!
43//! ## Node.js Usage
44//!
45//! The package works seamlessly in Node.js environments with ES modules or CommonJS.
46
47use briefcase_core::models::{DecisionSnapshot, Input, Output};
48use std::collections::HashMap;
49use wasm_bindgen::prelude::*;
50use web_sys::console;
51
52// Set up panic hook for better error messages in WASM
53#[cfg(feature = "console_error_panic_hook")]
54#[wasm_bindgen(start)]
55pub fn main() {
56    console_error_panic_hook::set_once();
57}
58
59/// Macro for console logging from WASM
60macro_rules! log {
61    ( $( $t:tt )* ) => {
62        console::log_1(&format!( $( $t )* ).into());
63    }
64}
65
66/// Initialize the Briefcase AI WASM module
67///
68/// This function sets up panic hooks for better error reporting in WebAssembly environments.
69/// Call this function once at the start of your application.
70///
71/// # Examples
72///
73/// ```javascript
74/// import { init } from 'briefcase-wasm';
75///
76/// await init();
77/// console.log('Briefcase AI ready!');
78/// ```
79#[wasm_bindgen]
80pub fn init() {
81    #[cfg(feature = "console_error_panic_hook")]
82    console_error_panic_hook::set_once();
83
84    log!("🦀 Briefcase AI WASM module initialized!");
85}
86
87/// JavaScript-friendly wrapper for DecisionSnapshot
88///
89/// Represents a snapshot of an AI decision with inputs, outputs, and metadata.
90/// This is the main data structure for tracking AI decisions in JavaScript/TypeScript.
91///
92/// # Examples
93///
94/// ```javascript
95/// import { JsDecisionSnapshot, JsInput, JsOutput } from 'briefcase-wasm';
96///
97/// const decision = new JsDecisionSnapshot("my_ai_function");
98/// const input = new JsInput("user_query", "Hello", "string");
99/// const output = new JsOutput("ai_response", "Hi there!", "string");
100///
101/// decision.add_input(input);
102/// decision.add_output(output);
103/// decision.set_execution_time(123.5);
104/// ```
105#[wasm_bindgen]
106pub struct JsDecisionSnapshot {
107    inner: DecisionSnapshot,
108}
109
110#[wasm_bindgen]
111impl JsDecisionSnapshot {
112    #[wasm_bindgen(constructor)]
113    pub fn new(function_name: &str) -> JsDecisionSnapshot {
114        JsDecisionSnapshot {
115            inner: DecisionSnapshot::new(function_name),
116        }
117    }
118
119    #[wasm_bindgen]
120    pub fn with_module(mut self, module_name: &str) -> JsDecisionSnapshot {
121        self.inner = self.inner.with_module(module_name);
122        self
123    }
124
125    #[wasm_bindgen]
126    pub fn add_input(
127        mut self,
128        name: &str,
129        value: &JsValue,
130        data_type: &str,
131    ) -> Result<JsDecisionSnapshot, JsError> {
132        let json_value: serde_json::Value = serde_wasm_bindgen::from_value(value.clone())
133            .map_err(|e| JsError::new(&format!("Failed to convert input value: {}", e)))?;
134
135        let input = Input::new(name, json_value, data_type);
136        self.inner = self.inner.add_input(input);
137        Ok(self)
138    }
139
140    #[wasm_bindgen]
141    pub fn add_output(
142        mut self,
143        name: &str,
144        value: &JsValue,
145        data_type: &str,
146    ) -> Result<JsDecisionSnapshot, JsError> {
147        let json_value: serde_json::Value = serde_wasm_bindgen::from_value(value.clone())
148            .map_err(|e| JsError::new(&format!("Failed to convert output value: {}", e)))?;
149
150        let output = Output::new(name, json_value, data_type);
151        self.inner = self.inner.add_output(output);
152        Ok(self)
153    }
154
155    #[wasm_bindgen]
156    pub fn add_tag(mut self, key: &str, value: &str) -> JsDecisionSnapshot {
157        self.inner = self.inner.add_tag(key, value);
158        self
159    }
160
161    #[wasm_bindgen]
162    pub fn to_json(&self) -> Result<JsValue, JsError> {
163        serde_wasm_bindgen::to_value(&self.inner)
164            .map_err(|e| JsError::new(&format!("Failed to serialize decision: {}", e)))
165    }
166}
167
168/// Simple in-memory storage for demo purposes
169#[wasm_bindgen]
170pub struct JsMemoryStorage {
171    decisions: HashMap<String, DecisionSnapshot>,
172}
173
174impl Default for JsMemoryStorage {
175    fn default() -> Self {
176        Self::new()
177    }
178}
179
180#[wasm_bindgen]
181impl JsMemoryStorage {
182    #[wasm_bindgen(constructor)]
183    pub fn new() -> JsMemoryStorage {
184        JsMemoryStorage {
185            decisions: HashMap::new(),
186        }
187    }
188
189    #[wasm_bindgen]
190    pub fn save_decision(&mut self, decision: &JsDecisionSnapshot) -> String {
191        let id = format!("decision_{}", uuid::Uuid::new_v4());
192        self.decisions.insert(id.clone(), decision.inner.clone());
193        id
194    }
195
196    #[wasm_bindgen]
197    pub fn load_decision(&self, decision_id: &str) -> Result<JsDecisionSnapshot, JsError> {
198        let decision = self
199            .decisions
200            .get(decision_id)
201            .ok_or_else(|| JsError::new(&format!("Decision not found: {}", decision_id)))?;
202
203        Ok(JsDecisionSnapshot {
204            inner: decision.clone(),
205        })
206    }
207
208    #[wasm_bindgen]
209    pub fn health_check(&self) -> bool {
210        true
211    }
212}
213
214/// Utility function to get the version of the WASM module
215#[wasm_bindgen]
216pub fn version() -> String {
217    env!("CARGO_PKG_VERSION").to_string()
218}
219
220/// Utility function for testing WASM functionality
221#[wasm_bindgen]
222pub fn test_functionality() -> Result<JsValue, JsError> {
223    // Create a test decision
224    let decision = JsDecisionSnapshot::new("test_function")
225        .with_module("test_module")
226        .add_tag("env", "test");
227
228    // Test storage
229    let mut storage = JsMemoryStorage::new();
230    let decision_id = storage.save_decision(&decision);
231    let _loaded_decision = storage.load_decision(&decision_id)?;
232
233    let result = serde_json::json!({
234        "decision_id": decision_id,
235        "storage_health": storage.health_check(),
236        "version": version(),
237    });
238
239    serde_wasm_bindgen::to_value(&result)
240        .map_err(|e| JsError::new(&format!("Failed to serialize test result: {}", e)))
241}