dx_forge/api/
reactivity.rs

1//! Triple-Path Reactivity Engine APIs
2
3use anyhow::Result;
4use std::sync::{Arc, OnceLock};
5use parking_lot::RwLock;
6use tokio::time::{Duration, sleep};
7use std::path::PathBuf;
8
9/// Reactivity state management
10static REACTIVITY_STATE: OnceLock<Arc<RwLock<ReactivityState>>> = OnceLock::new();
11
12struct ReactivityState {
13    in_batch: bool,
14    batch_start: Option<std::time::Instant>,
15}
16
17impl Default for ReactivityState {
18    fn default() -> Self {
19        Self {
20            in_batch: false,
21            batch_start: None,
22        }
23    }
24}
25
26fn get_reactivity_state() -> Arc<RwLock<ReactivityState>> {
27    REACTIVITY_STATE.get_or_init(|| Arc::new(RwLock::new(ReactivityState::default()))).clone()
28}
29
30/// Instant path — called on every DidChangeTextDocument
31///
32/// Triggers immediate tool execution for realtime feedback (e.g., syntax highlighting, diagnostics).
33pub fn trigger_realtime_event(file: PathBuf, _content: String) -> Result<()> {
34    tracing::debug!("⚡ Realtime event: {:?}", file);
35    
36    // TODO: Queue for immediate execution
37    // This would trigger tools marked for realtime execution
38    
39    Ok(())
40}
41
42/// 300ms debounce — safe default for style, lint, format
43///
44/// Triggers tool execution after a 300ms debounce period to avoid excessive runs.
45pub async fn trigger_debounced_event(file: PathBuf, _content: String) -> Result<()> {
46    tracing::debug!("⏱️  Debounced event: {:?} (300ms)", file);
47    
48    // Wait for debounce period
49    sleep(Duration::from_millis(300)).await;
50    
51    // TODO: Execute debounced tools
52    
53    Ok(())
54}
55
56/// Only when user idle ≥2s — i18n, security, bundle analysis
57///
58/// Triggers tool execution only when the user has been idle for at least 2 seconds.
59pub async fn trigger_idle_event(file: PathBuf) -> Result<()> {
60    tracing::debug!("😴 Idle event: {:?} (≥2s idle)", file);
61    
62    // Wait for idle period
63    sleep(Duration::from_secs(2)).await;
64    
65    // TODO: Execute idle-tier tools
66    
67    Ok(())
68}
69
70/// Marks start of atomic multi-file operation
71///
72/// Batches multiple file changes together to avoid redundant tool executions.
73pub fn begin_batch_operation() -> Result<()> {
74    let state = get_reactivity_state();
75    let mut state = state.write();
76    
77    tracing::info!("📦 Beginning batch operation");
78    state.in_batch = true;
79    state.batch_start = Some(std::time::Instant::now());
80    
81    Ok(())
82}
83
84/// Marks completion — triggers idle queue + resets branching
85///
86/// Ends the batch operation and triggers all queued events.
87pub fn end_batch_operation() -> Result<()> {
88    let state = get_reactivity_state();
89    let mut state = state.write();
90    
91    if let Some(start) = state.batch_start {
92        let duration = start.elapsed();
93        tracing::info!("✅ Batch operation completed in {:.2}s", duration.as_secs_f64());
94    }
95    
96    state.in_batch = false;
97    state.batch_start = None;
98    
99    // TODO: Flush all queued events
100    
101    Ok(())
102}
103
104#[cfg(test)]
105mod tests {
106    use super::*;
107    
108    #[test]
109    fn test_batch_operation() {
110        begin_batch_operation().unwrap();
111        end_batch_operation().unwrap();
112    }
113    
114    #[tokio::test]
115    async fn test_debounced_event() {
116        let file = PathBuf::from("test.ts");
117        let result = trigger_debounced_event(file, "content".to_string()).await;
118        assert!(result.is_ok());
119    }
120}