Skip to main content

busbar_sf_agentscript/graph/
wasm.rs

1//! WebAssembly bindings for the AgentScript graph analysis library.
2//!
3//! This module provides thin JavaScript-accessible wrappers around the core
4//! graph functionality. All actual logic lives in other modules:
5//! - `render/` - ASCII and GraphML rendering
6//! - `export` - Serialization types
7//! - Core crate - Graph building, validation, queries
8
9use super::{export, render, RefGraph};
10use wasm_bindgen::prelude::*;
11
12// ============================================================================
13// Graph building
14// ============================================================================
15
16/// Build a reference graph from an AgentScript AST.
17#[wasm_bindgen]
18pub fn build_graph(ast: JsValue) -> Result<JsValue, JsValue> {
19    let agent: crate::AgentFile = serde_wasm_bindgen::from_value(ast)
20        .map_err(|e| JsValue::from_str(&format!("Failed to deserialize AST: {}", e)))?;
21
22    let graph = RefGraph::from_ast(&agent)
23        .map_err(|e| JsValue::from_str(&format!("Failed to build graph: {}", e)))?;
24
25    let repr = export::GraphRepr::from(&graph);
26    serde_wasm_bindgen::to_value(&repr)
27        .map_err(|e| JsValue::from_str(&format!("Serialization error: {}", e)))
28}
29
30/// Build a reference graph from AgentScript source code.
31#[wasm_bindgen]
32pub fn build_graph_from_source(source: &str) -> Result<JsValue, JsValue> {
33    let graph = parse_and_build(source)?;
34    let repr = export::GraphRepr::from(&graph);
35    serde_wasm_bindgen::to_value(&repr)
36        .map_err(|e| JsValue::from_str(&format!("Serialization error: {}", e)))
37}
38
39// ============================================================================
40// Validation
41// ============================================================================
42
43/// Validate a reference graph and return any errors/warnings.
44#[wasm_bindgen]
45pub fn validate_graph(source: &str) -> Result<JsValue, JsValue> {
46    let graph = parse_and_build(source)?;
47    let result = graph.validate();
48    let repr = export::ValidationResultRepr::from(&result);
49    serde_wasm_bindgen::to_value(&repr)
50        .map_err(|e| JsValue::from_str(&format!("Serialization error: {}", e)))
51}
52
53// ============================================================================
54// Statistics
55// ============================================================================
56
57/// Get statistics about a reference graph.
58#[wasm_bindgen]
59pub fn get_graph_stats(source: &str) -> Result<JsValue, JsValue> {
60    let graph = parse_and_build(source)?;
61    let stats = graph.stats();
62    serde_wasm_bindgen::to_value(&stats)
63        .map_err(|e| JsValue::from_str(&format!("Serialization error: {}", e)))
64}
65
66// ============================================================================
67// Queries
68// ============================================================================
69
70/// Find all usages of a topic by name.
71#[wasm_bindgen]
72pub fn find_topic_usages(source: &str, topic_name: &str) -> Result<JsValue, JsValue> {
73    let graph = parse_and_build(source)?;
74
75    let topic_idx = graph
76        .get_topic(topic_name)
77        .ok_or_else(|| JsValue::from_str(&format!("Topic '{}' not found", topic_name)))?;
78
79    let usages = graph.find_usages(topic_idx);
80    let nodes: Vec<export::NodeRepr> = usages
81        .nodes
82        .iter()
83        .filter_map(|&idx| graph.get_node(idx).map(export::NodeRepr::from))
84        .collect();
85
86    serde_wasm_bindgen::to_value(&nodes)
87        .map_err(|e| JsValue::from_str(&format!("Serialization error: {}", e)))
88}
89
90/// Find all topics that a given topic transitions to.
91#[wasm_bindgen]
92pub fn find_topic_transitions(source: &str, topic_name: &str) -> Result<JsValue, JsValue> {
93    let graph = parse_and_build(source)?;
94
95    let topic_idx = graph
96        .get_topic(topic_name)
97        .ok_or_else(|| JsValue::from_str(&format!("Topic '{}' not found", topic_name)))?;
98
99    let transitions = graph.find_outgoing_transitions(topic_idx);
100    let nodes: Vec<export::NodeRepr> = transitions
101        .nodes
102        .iter()
103        .filter_map(|&idx| graph.get_node(idx).map(export::NodeRepr::from))
104        .collect();
105
106    serde_wasm_bindgen::to_value(&nodes)
107        .map_err(|e| JsValue::from_str(&format!("Serialization error: {}", e)))
108}
109
110/// Find all usages (readers and writers) of a variable by name.
111#[wasm_bindgen]
112pub fn find_variable_usages(source: &str, var_name: &str) -> Result<String, JsValue> {
113    let graph = parse_and_build(source)?;
114
115    let var_idx = graph
116        .get_variable(var_name)
117        .ok_or_else(|| JsValue::from_str(&format!("Variable '{}' not found", var_name)))?;
118
119    let readers = graph.find_variable_readers(var_idx);
120    let writers = graph.find_variable_writers(var_idx);
121
122    let result = export::VariableUsagesRepr {
123        readers: readers
124            .nodes
125            .iter()
126            .filter_map(|&idx| graph.get_node(idx).map(export::UsageInfoRepr::from_node))
127            .collect(),
128        writers: writers
129            .nodes
130            .iter()
131            .filter_map(|&idx| graph.get_node(idx).map(export::UsageInfoRepr::from_node))
132            .collect(),
133    };
134
135    serde_json::to_string(&result)
136        .map_err(|e| JsValue::from_str(&format!("Serialization error: {}", e)))
137}
138
139// ============================================================================
140// Rendering (ASCII)
141// ============================================================================
142
143/// Render the topic flow graph as ASCII art.
144#[wasm_bindgen]
145pub fn render_topic_flow(source: &str) -> Result<String, JsValue> {
146    let graph = parse_and_build(source)?;
147    Ok(render::render_topic_flow(&graph))
148}
149
150/// Render a detailed execution graph as ASCII art.
151#[wasm_bindgen]
152pub fn render_graph(source: &str, view: &str) -> Result<String, JsValue> {
153    let graph = parse_and_build(source)?;
154
155    match view {
156        "topics" => Ok(render::render_topic_flow(&graph)),
157        "actions" => Ok(render::render_actions_view(&graph)),
158        "full" => Ok(render::render_full_view(&graph)),
159        _ => Err(JsValue::from_str("Invalid view type. Use 'topics', 'actions', or 'full'")),
160    }
161}
162
163// ============================================================================
164// Export (JSON)
165// ============================================================================
166
167/// Export the graph structure as JSON for visualization/GraphQL consumption.
168#[wasm_bindgen]
169pub fn export_graph_json(source: &str) -> Result<String, JsValue> {
170    let graph = parse_and_build(source)?;
171    let export = export::GraphExport::from_graph(&graph);
172    serde_json::to_string_pretty(&export)
173        .map_err(|e| JsValue::from_str(&format!("JSON serialization error: {}", e)))
174}
175
176/// Compact JSON export (no pretty printing).
177#[wasm_bindgen]
178pub fn export_graph_json_compact(source: &str) -> Result<String, JsValue> {
179    let graph = parse_and_build(source)?;
180    let repr = export::GraphRepr::from(&graph);
181    serde_json::to_string(&repr)
182        .map_err(|e| JsValue::from_str(&format!("JSON serialization error: {}", e)))
183}
184
185// ============================================================================
186// Export (GraphML)
187// ============================================================================
188
189/// Export the reference graph as GraphML format.
190///
191/// GraphML is an XML-based format supported by yEd, Gephi, Cytoscape, etc.
192#[wasm_bindgen]
193pub fn export_graphml(source: &str) -> Result<String, JsValue> {
194    let graph = parse_and_build(source)?;
195    Ok(render::render_graphml(&graph))
196}
197
198// ============================================================================
199// Dependencies
200// ============================================================================
201
202/// Extract all Salesforce org dependencies from AgentScript source.
203#[wasm_bindgen]
204pub fn extract_dependencies(source: &str) -> Result<JsValue, JsValue> {
205    let agent = crate::parse(source).map_err(|errs| JsValue::from_str(&errs.join("\n")))?;
206
207    let report = super::dependencies::extract_dependencies(&agent);
208    serde_wasm_bindgen::to_value(&report)
209        .map_err(|e| JsValue::from_str(&format!("Serialization error: {}", e)))
210}
211
212/// Check if a specific SObject is used in the source.
213#[wasm_bindgen]
214pub fn uses_sobject(source: &str, sobject_name: &str) -> Result<bool, JsValue> {
215    let agent = crate::parse(source).map_err(|errs| JsValue::from_str(&errs.join("\n")))?;
216    let report = super::dependencies::extract_dependencies(&agent);
217    Ok(report.uses_sobject(sobject_name))
218}
219
220/// Check if a specific Flow is used in the source.
221#[wasm_bindgen]
222pub fn uses_flow(source: &str, flow_name: &str) -> Result<bool, JsValue> {
223    let agent = crate::parse(source).map_err(|errs| JsValue::from_str(&errs.join("\n")))?;
224    let report = super::dependencies::extract_dependencies(&agent);
225    Ok(report.uses_flow(flow_name))
226}
227
228/// Check if a specific Apex class is used in the source.
229#[wasm_bindgen]
230pub fn uses_apex_class(source: &str, class_name: &str) -> Result<bool, JsValue> {
231    let agent = crate::parse(source).map_err(|errs| JsValue::from_str(&errs.join("\n")))?;
232    let report = super::dependencies::extract_dependencies(&agent);
233    Ok(report.uses_apex_class(class_name))
234}
235
236// ============================================================================
237// Utility
238// ============================================================================
239
240/// Get the version of the graph library.
241#[wasm_bindgen]
242pub fn graph_version() -> String {
243    env!("CARGO_PKG_VERSION").to_string()
244}
245
246// ============================================================================
247// Internal helpers
248// ============================================================================
249
250/// Parse source and build graph - common helper to reduce duplication.
251fn parse_and_build(source: &str) -> Result<RefGraph, JsValue> {
252    let agent = crate::parse(source).map_err(|errs| JsValue::from_str(&errs.join("\n")))?;
253
254    RefGraph::from_ast(&agent)
255        .map_err(|e| JsValue::from_str(&format!("Failed to build graph: {}", e)))
256}