1use briefcase_core::models::{DecisionSnapshot, Input, Output};
48use std::collections::HashMap;
49use wasm_bindgen::prelude::*;
50use web_sys::console;
51
52#[cfg(feature = "console_error_panic_hook")]
54#[wasm_bindgen(start)]
55pub fn main() {
56 console_error_panic_hook::set_once();
57}
58
59macro_rules! log {
61 ( $( $t:tt )* ) => {
62 console::log_1(&format!( $( $t )* ).into());
63 }
64}
65
66#[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#[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#[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#[wasm_bindgen]
216pub fn version() -> String {
217 env!("CARGO_PKG_VERSION").to_string()
218}
219
220#[wasm_bindgen]
222pub fn test_functionality() -> Result<JsValue, JsError> {
223 let decision = JsDecisionSnapshot::new("test_function")
225 .with_module("test_module")
226 .add_tag("env", "test");
227
228 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}
242
243pub mod client;
245
246pub mod cost;
248pub mod drift;
249pub mod models;
250pub mod sanitization;
251
252#[cfg(test)]
253mod tests {
254 use super::*;
255
256 #[test]
257 fn test_version_returns_package_version() {
258 let v = version();
259 assert!(!v.is_empty());
260 assert!(v.contains('.'), "Version '{}' should contain dots", v);
262 }
263
264 #[test]
265 fn test_js_decision_snapshot_construction() {
266 let decision = JsDecisionSnapshot::new("test_fn");
267 assert_eq!(decision.inner.function_name, "test_fn");
268 assert!(decision.inner.module_name.is_none());
269 }
270
271 #[test]
272 fn test_js_decision_snapshot_with_module() {
273 let decision = JsDecisionSnapshot::new("test_fn").with_module("test_mod");
274 assert_eq!(decision.inner.module_name, Some("test_mod".to_string()));
275 }
276
277 #[test]
278 fn test_js_decision_snapshot_add_tag() {
279 let decision = JsDecisionSnapshot::new("test_fn")
280 .add_tag("env", "production")
281 .add_tag("version", "1.0");
282 assert_eq!(
283 decision.inner.tags.get("env"),
284 Some(&"production".to_string())
285 );
286 assert_eq!(decision.inner.tags.get("version"), Some(&"1.0".to_string()));
287 }
288
289 #[test]
290 fn test_js_memory_storage_new() {
291 let storage = JsMemoryStorage::new();
292 assert!(storage.health_check());
293 }
294
295 #[test]
296 fn test_js_memory_storage_save_and_load() {
297 let decision = JsDecisionSnapshot::new("persist_fn")
298 .with_module("persist_mod")
299 .add_tag("key", "value");
300
301 let mut storage = JsMemoryStorage::new();
302 let id = storage.save_decision(&decision);
303
304 assert!(id.starts_with("decision_"));
305
306 let loaded = storage.load_decision(&id).unwrap();
307 assert_eq!(loaded.inner.function_name, "persist_fn");
308 assert_eq!(loaded.inner.module_name, Some("persist_mod".to_string()));
309 assert_eq!(loaded.inner.tags.get("key"), Some(&"value".to_string()));
310 }
311
312 #[test]
313 #[cfg(target_arch = "wasm32")]
314 fn test_js_memory_storage_load_nonexistent() {
315 let storage = JsMemoryStorage::new();
316 let result = storage.load_decision("nonexistent_id");
317 assert!(result.is_err());
318 }
319
320 #[test]
321 fn test_js_memory_storage_multiple_decisions() {
322 let mut storage = JsMemoryStorage::new();
323
324 let d1 = JsDecisionSnapshot::new("fn_1");
325 let d2 = JsDecisionSnapshot::new("fn_2");
326 let d3 = JsDecisionSnapshot::new("fn_3");
327
328 let id1 = storage.save_decision(&d1);
329 let id2 = storage.save_decision(&d2);
330 let id3 = storage.save_decision(&d3);
331
332 assert_ne!(id1, id2);
334 assert_ne!(id2, id3);
335 assert_ne!(id1, id3);
336
337 assert_eq!(
339 storage.load_decision(&id1).unwrap().inner.function_name,
340 "fn_1"
341 );
342 assert_eq!(
343 storage.load_decision(&id2).unwrap().inner.function_name,
344 "fn_2"
345 );
346 assert_eq!(
347 storage.load_decision(&id3).unwrap().inner.function_name,
348 "fn_3"
349 );
350 }
351
352 #[test]
353 fn test_js_memory_storage_default_trait() {
354 let storage = JsMemoryStorage::default();
355 assert!(storage.health_check());
356 }
357}